“Error #1088” despite a well-formed XML file

The 1 line of code below in my XML processor class caused this error: “Error #1088: The markup in the document following the root element must be well-formed,” despite the fact that my source xml file passed multiple validation tests from multiple sources.

fEvent = XML(xmlData.fevnt);		

As soon as I got rid of the casting operation XML (…) and changed the fEvent datatype to String or Array ( a cheap trick to access XML element attributes), the error went away.

When I googled “Error #1088” most posts were about XML that was dynamically generated via a server-side script. In this instance, this was a static, well-formed XML file that suddenly started throwing this error, so I had to go through the code, commenting it out line by line to find the culprit. Who knew — google doesn’t have the answers to everything all the time! =)

Some context for the problem code, as it existed in the XML processor class:

//...

private function loadData(xmlFilePath:String):void {
			loader = new URLLoader();
			reqst=new URLRequest(xmlFilePath);
			loader.load(reqst);
			loader.addEventListener(Event.COMPLETE, onLdrComplete);
}

private function onLdrComplete(e:Event):void {
			var ldr:URLLoader=e.target as URLLoader;
			xmlData=new XML(ldr.data); //defined as xmlData:XML above
			//...
            finalEvent = XML(xmlData.finalevnt);				
}

Implementing tracking pixels in AS3.0: Mediaplex

Embedding tracking pixels in a Flash app sounds like it should be simple. Just make a Request to the server from your .swf that calls up the 1×1 .gif, right? Somehow or other it ends up being a confusing process nonetheless.

In my humble experience, the oversight is usually on the side of the vendor whose technology a Developer is asked to implement. Simple requests for instructions on how to implement a 3rd party technology into an Actionscript 3.0 application are met with confused email responses by folks who don’t seem to understand that there’s more than one version of the Actionscript programming language and the occasional emailed PDF that describes things like “How to Add a clickTag” using screen shots from Flash 5 and Asctionscript 1.0. Since more often than not vendors can’t or won’t send clear instructions on how to implement their tags in the current version of Actionscript / Flash / Flex, what ends up happening is the Developer is asked to test and debug the vendor’s product for them, for free.

Going forward, it’d be nice if a company that sells technology which they say can be implemented in Flash could actually have someone in that company:

  1. create a modern Flash application
  2. implement the technology
  3. have the source files ready for clients to use for reference.

OK, enough venting.

Here’s one way to implement Mediaplex tracking pixels in AS3.0 (Flash / Flex)

Make sure you receive the Standard pixel path, i.e. one that starts with “<img” (not one that starts with “<iframe”). Here’s a basic test class that calls a Mediaplex tracking pixel:

package
{
	import flash.net.URLRequest;
	import flash.net.URLVariables;
	import flash.net.URLLoader;
	import flash.net.URLRequestMethod;
	import flash.display.Sprite;

	[SWF (width="500", height="750", backgroundColor="#000000", frameRate="31")]
	public class Main extends Sprite
	{
		private var yourMediaPlexURL:Array = ["http://sd.mediaplex.com/somepath/"];

		public function Main():void
		{
			Security.loadPolicyFile("http://www.your.com/crossdomain.xml");
			this.addEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
		}

		public function  onAddedToStage(e:Event):void
		{
			//call the tracking code
			trackMediaPlex(yourMediaPlexURL[0]);
		}

		public function trackMediaPlex(yourURL:String):void
		{
			var myRequest:URLRequest = new URLRequest(yourURL);
			myRequest.method = URLRequestMethod.POST;

			var myLoader:URLLoader = new URLLoader();
			try{
				myLoader.load(myRequest);
				trace("\t trackMediaPlex(): myRequest.url = " + myRequest.url);
			} catch(e:Error) {
				trace(e.getStackTrace());
			}
		}
	}
}

Before activating the pixel, it’s a good idea to quickly test it in Firefox using Firebug. Under the Net tab[1] in in Firebug you should see three server hits:

  1. your original request
  2. a redirect
  3. a request for the 1×1 pixel file itself.

Once you see these firing in Firebug, activate the pixel and you should be good to go. For more detailed testing use Charles (Mac) and Fiddler (PC).

A basic crossdomain.xml policy file should allow all the potential Mediaplex urls that might be used, such as this one: http://somedomain.mediaplex.com. Be careful about how you specify urls in crossdomain.xml, the minuscule difference between “http://somedomain.mediaplex.com&#8221; and “http://somedomain.mediaplex.com/&#8221; can break your code.

Notes:
1. Make sure you Enable the Net tab first

Faking abstract classes in AS3: a slideshow app

Here’s a quick little exercise in writing an AS3 application. It’s a basic image slide show I wrote as a “code only” app in Flash CS4. There’s nothing in the Library, nothing on the stage. The .fla is only used to specify a Document Class (BasicSlideShow) and compile the SWF. The code can also be compiled via Flex Builder 3, FlashDevelop, FDT or Flash Builder with minimal change (mostly the metadata stuff like [SWF (width=”600″, height=”400″, backgroundColor=”#9999cc”, frameRate=”31″)]).

Faking an abstract class in AS3.0.

AS3.0 only supports Interfaces, so that’s what I started out with at first. However, soon enough I found myself repeating code (always inefficient and error prone). So I switched to a simulated abstract class to combine the benefits of an Interface with the code reuse utility offered by these puppies:

package com.timshaya.model
{
	import gs.TweenLite;
	import flash.display.Sprite;
	import flash.net.URLRequest;
	import flash.net.navigateToURL;
	import flash.events.MouseEvent;

	//DO NOT implement, subclass instead
	public class AbstractSlide extends Sprite
	{
		protected var _path:String;
		protected var _link:String;

		public function get path():String { return _path; }
		public function set path(val:String):void { _path = val; }
		public function get link():String { return _link; }
		public function set link(val:String):void { _link = val; }

		public function AbstractSlide(path2:String="", link2:String=""){}

		//faking an abstract method in AS3.0
        public function setClickArea():void
		{
			throw new Error("AbstractSlide shouldn't be implemented. Subclass it insteaad and override this method: setClickArea");
		}

		//faking an abstract method in AS3.0
		public function doLink(e:MouseEvent):void
		{
			throw new Error("AbstractSlide shouldn't be implemented. Subclass it insteaad and override this method: doLink");
		}

		//faking an abstract method in AS3.0
		public function animateSlideIn(spd:Number):void
		{
			throw new Error("AbstractSlide shouldn't be implemented. Subclass it insteaad and override this method: animateSlideIn");
		}

		public function fadeInSlide(speed:Number):void
		{
			this.alpha = 0;
			TweenLite.to(this, speed, {alpha:1});
                //using this static method enables TweenLite's automatic memory management
		}
	}
}

Here’s a subclass that uses AbstractSlide:

package com.timshaya.model
{
	import flash.display.Sprite;
	import flash.net.URLRequest;
	import flash.net.navigateToURL;
	import flash.events.MouseEvent;

	public class SimpleSlide extends AbstractSlide
	{
		public function SimpleSlide(path2:String = "", link2:String = "")
		{
			this.path = path2;
			this.link = link2;
			this.setClickArea();
		}

		public override function setClickArea():void
		{
			this.buttonMode = true;
			this.useHandCursor = true;
			this.addEventListener(MouseEvent.CLICK, doLink, false, 0, true);
		}

		public override function doLink(e:MouseEvent):void
		{
			var url:String = this.link; //grab url from getter method
			var req:URLRequest = new URLRequest(url);

			try {
				navigateToURL(req, '_self');
			} catch (e:Error) {
				trace("Something's wrong with the URL!");
			}
		}

		public override function animateSlideIn(spd:Number):void
		{
			fadeInSlide(spd);
		}
	}
}

The bulk of the business logic is in this class (the Document Class in this case):

package
{
	import com.timshaya.model.events.SlideRotator;
	import com.timshaya.model.SimpleSlide;

	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.ProgressEvent;
	import flash.net.URLLoader;
	import flash.net.URLRequest;
	import flash.net.URLLoaderDataFormat;
	import flash.display.Loader;

	public class BasicSlideShow extends Sprite
	{
		private var xmlPath:String;

		private var firstTime:Boolean = true;
		private var slideShow:SlideRotator;
		private var slideContainer:Sprite;
		private var currntSlide:int;
		private var currntImg:Sprite;
		private var myImages:Array = new Array();
		private var myImg:Sprite;
		private var loader:URLLoader;

		public function set theXmlPath(val:String):void { xmlPath = val; }
		public function get theXmlPath():String { return xmlPath; }

		public function BasicSlideShow(defaultPath:String = "slideshow.xml")
		{
			this.theXmlPath = defaultPath;
			buildImgList();
		}

		public function buildImgList():void
		{
			currntSlide = 0;
			slideContainer = new Sprite();
			slideContainer.x = slideContainer.y = 0;
			addChild(slideContainer);

			loadAllImages(); //load external images
		}

		public function loadAllImages():void
		{
			//load XML list of images (with paths & urls)
			var xmlURL:URLRequest = new URLRequest(this.theXmlPath);
			loader = new URLLoader();
            loader.dataFormat = URLLoaderDataFormat.TEXT;  
			loader.addEventListener(Event.COMPLETE, onXMLDoneLoading, false, 0, true);
			loader.load(xmlURL);
		}

		public function onXMLDoneLoading(e:Event):void
		{
			loader.removeEventListener(Event.COMPLETE, onXMLDoneLoading); //free up memory slot

			var myXml = new XML(e.target.data);
			for(var g:int=0; g < myXml.image.length(); g++)
			{
				trace("g = " + g);
				myImg = new SimpleSlide();
				myImages.push(myImg);
              // make myImages as long as the num of images in XML file
			}

			for each (var img:Sprite in myImages) //add img containers to main display object
			{
				img.visible = false;
				slideContainer.addChild(img);
			}

			for(var i:int=0; i < myXml.image.length(); i++)
			{
				SimpleSlide(myImages[i]).path = myXml.image[i].path.@src;
				SimpleSlide(myImages[i]).link = myXml.image[i].url.@src;

				var imgLoader = new Loader(); //load the actual JPEGs
				imgLoader.load(new URLRequest(myImages[i].path));
				imgLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, imageLoaded, false, 0, true);
				imgLoader.contentLoaderInfo.addEventListener(ProgressEvent.PROGRESS, imageLoadProgress, false, 0, true);
				myImages[i].addChild(imgLoader);
			}

			doneLoading();
		}

		public function imageLoaded(e:Event):void { trace("imageLoaded"); }

		public function imageLoadProgress(e:ProgressEvent):void
		{
			trace((e.target.bytesLoaded / e.target.bytesTotal)*100 + "%"); 
            // cheezy; good for demo purposes
		}

		public function doneLoading():void
		{
			slideShow = new SlideRotator(); //begin main slideshow timer
			slideShow.addEventListener(SlideRotator.CHANGE_SLIDE, notifyAll, false, 0, true);
		}

		public function nextSlide():void
		{
		    for each (var img:Sprite in myImages) //hide all images
				img.visible = false;

			currntImg = myImages[currntSlide];//select next image to show
			currntImg.visible = true;
			SimpleSlide(currntImg).animateSlideIn(1.33);
		}

		public function notifyAll(e:Event):void
		{
			trace("BasicSlideShow.notifyAll says: SlideRotator dispatch tells us to change slide");

			if(currntSlide < myImages.length-1){  //increment currntSlide
				if(firstTime) {
					currntSlide = 0;
					firstTime = false;
				} else {
					currntSlide += 1;
				}
			} else {
				currntSlide = 0;
			}
			trace("currntSlide = " + currntSlide);

			nextSlide();

			//updateNavbar();
		}

	}
}

NOTE: the image loading code can and should be cleaned up.

 

Most of the custom event flow is in SlideRotator, which extends EventDispatcher:

package com.timshaya.model.events
{
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.TimerEvent;
	import flash.utils.Timer;

	public class SlideRotator extends EventDispatcher
	{
		public static const TIMER_DELAY:int = 5000;
		public static const CHANGE_SLIDE:String = "changeSlide";

		private var timeCount:Timer;

		public function SlideRotator()
		{
			timeCount = new Timer(TIMER_DELAY);
			timeCount.addEventListener(TimerEvent.TIMER, onTimer, false, 0, true);
			timeCount.start();
		}

		public function onTimer(e:TimerEvent):void
		{
			changeSlide();
		}

		public function changeSlide():void
		{
			dispatchEvent(new Event(SlideRotator.CHANGE_SLIDE));
		}

		public function pauseTimer():void
		{
			timeCount.stop();
		}

	    public function restartTimer():void
		{
			timeCount.start();
		}
	}
}

Here’s the XML source file:

<?xml version="1.0" encoding="UTF-8"?>
<slideshow>
	<image type="1">
		<path src="img/3.jpg"/>
		<url src="http://www.ft.com"/>
	</image>
	<image type="1">
		<path src="img/4.jpg"/>
		<url src="http://www.google.com"/>
	</image>
	<image type="1">
		<path src="img/b1.jpg"/>
		<url src="http://www.yahoo.com"/>
	</image>
	<image type="1">
		<path src="img/b2.jpg"/>
		<url src="http://www.guardian.co.uk"/>
	</image>
	<image type="1">
		<path src="img/b3.jpg"/>
		<url src="http://www.gmail.com"/>
	</image>
	<image type="1">
		<path src="img/Slot2_Flash_template1.jpg"/>
		<url src="http://www.nytimes.com"/>
	</image>
	<image type="1">
		<path src="img/c3.jpg"/>
		<url src="http://www.yahoo.com"/>
	</image>
	<image type="1">
		<path src="img/d3.jpg"/>
		<url src="http://www.amazon.com"/>
	</image>
	<image type="1">
		<path src="img/d3.jpg"/>
		<url src="http://www.amazon.com"/>
	</image>
</slideshow>

You can put any number of images in the “img” directory, BasicSlideShow.as counts the number of images via the XML file.

To build upon this exercise, you can extend AbstractSlide and make your own CustomSlide. For example, if you need a slide with 2 links instead of 1, you can use an XML image tag of type=”2″ and do some basic refactoring to make sure BasicSlideShow catches it. Yep, you can also write an AbstractSlideShow, so instead of refactoring, you’d be adding another implementation while keeping the old one, in case you need it later. Ofcourse, you can add your own NavBar, CustomBtn classes, Pause/Play buttons, etc.

Optimized XML-based AS3 Carousel

Here’s a quick way to optimize this AS3 carousel-style image gallery / menu.

After this change to Main.as there was no need to manually update the number of images in the carousel’s main class file. Just change drop the new images into the images folder & add their names as XML elements & you’re done.

  1. On line 29, change “private var numOfItems:Number = 3;” to “private var numOfItems:Number;”
  2. After line 58, add “numOfItems = imglist.length();” and let the code calculate how many elements are in your xml file
//...some code here

private var numOfItems:Number; //line 29
//...some code here

private function loadBottles(e:Event):void
{
       // pull in the xml file and load the song data
        xml = new XML(e.target.data);
			
        var imglist:XMLList = xml.bottleimages.@image; //line 58
			
	numOfItems = imglist.length();

       //some code here...

}

//some code here...

Since imglist is of type XMLList it has a built-in length() method which acts like a similar Array method and returns the number of items in the collection.