Scott Morgan

LA Flash and Flex Developer

Archive for the ‘Source Code’ Category

I have been meaning to add this to my blog for quite some time now. I have read a few postings related to this topic but I am surprised there aren’t a lot more people complaining about this. Personally I feel Adobe really dropped the ball with this bug. I guess I should explain what the bug is before I go on.

Lets pretend we’re creating a button, now I know most of my readers are developers and for the most part we would never create a simple button like this using multiple frames on the timeline. But we all know that designers love to use the timeline and create button states using frames and frame labels. I actually think this posting will help designers as much as it will help the developers who are working with said designers.

Now lets pretend our button has 3 states, up, over, and down and each state is represented on the timeline, 5 frames apart and the frame is labeled the same as each state. So the first frames label is “up”, the 5th frames label is “over”, and the 10th frames label is “down”. On each frame there is a MovieClip named button and each key frame has the same graphic just tinted differently to differentiate the states. Then on our code layer we set up MouseEvents for each state MOUSE_OVER, MOUSE_OUT, and MOUSE_DOWN, in each of the event handlers we move the playhead to each frame using the gotoAndStop method while passing in the frame label that we want the playhead to move to. Ok, so this is all straight forward. Very flash 4. The interesting part of this is when you try to access sibling displayObjects on the same frame right after we call gotoAndStop.

To illustrate this bug lets add a layer below the layer that has our button on it. On this layer add a keyframe at frame 5 and add a MovieClip, whatever you want, import a picture draw a shape, it doesn’t matter. Just as long as you give it an instance name of overClip. Right after we call gotoAndStop(’over’) try and trace (overClip). If you are not completely following me right now here is a picture of what my timeline looks like:

And our code will look something like this (excuse the timeline code, but it is the easiest way to demonstrate this bug).

button.addEventListener(MouseEvent.MOUSE_OVER, rollover);
button.addEventListener(MouseEvent.MOUSE_OUT, rollout);

function rollover(e:MouseEvent):void {
     gotoAndStop('over');
     trace('the instance name of the overclip is: ' + this.overClip.name);
}

function rollout(e:MouseEvent):void {
     gotoAndStop('up');
}

In the rollover function you will notice I added a trace that traces out the name property of the overClip that appears on the out frame (or frame 5 for those of you who like numbers). If you were to run this code you will notice that the rollover works, the playhead moves to frame 5 and visually you see the change, however, the trace statement throws the following error “TypeError: Error #1009: Cannot access a property or method of a null object reference.” Null?? How can that be, the playhead is at frame 5, and the overClip is on frame 5, why is overClip null? Good question! The other odd thing is if you trace out numChildren right after the gotoAndStop(’over’) call it will return the correct number of child displayObjects. How can the player know how many children there is but still throw an error saying we’re trying to access a null object reference?

This was a very common practice in AS2, especially in highly creative sites with both Flash Designers and Flash Developers. And as more creative shops start embracing Flash 9 I think this issue will become more common.

Well, the first thing I thought to do was to call stage.invalidate() before I called gotoAndStop(’over’), in theory this should force a redraw and fire the render event. Well it did fire the render event but I still couldn’t trace out the clip on frame 5. Arghhh!

The one thing I did notice is the error is only thrown the first time rollover is called. Everytime after that the reference to overClip traces out fine. So this got me thinking. It looks like displayObjects do not register until the current frame cycle is complete. For those who do not know, a frame cycle is two parts, first the code executes, once the code on that particular frame executes any display updates are executed (adding overClip to the timeline is a display update).

The first approach to get around this bug I thought up was to kick off a EnterFrame event in the rollever function, after the EnterFrame event is fired, it still didn’t trace out overClip. Then I thought maybe I need a second frame cycle to register everything because technically the EnterFrame event fires at the exact moment the playhead hits the frame which is exactly the same problem we already have. Ok, so lets allow for two EnterFrame events to fire, kill the event, and trace out overClip. Guess what, that works. Here is the code for this approach.

var count = 0;

button.addEventListener(MouseEvent.MOUSE_OVER, rollover);
button.addEventListener(MouseEvent.MOUSE_OUT, rollout);

function rollover(e:MouseEvent):void {
     count = 0;
     gotoAndStop('over');
     addEventListener(Event.ENTER_FRAME, rolloverDelay);
}

function rolloverDelay(e:Event):void {
     count++
     if (count == 2) {
          removeEventListener(Event.ENTER_FRAME, rolloverDelay);
          trace('the instance name of the overclip is: ' + this.overClip.name);
     }
}

function rollout(e:MouseEvent):void {
     gotoAndStop('up');
}

Yes this works, but imagine how complex this mess of code would get if we had a complex 12 state button with animations on each state. Not very practical.

The only way I could come up with to get around this issue was to create an EnterFrame that fires right off the bat, moves the playhead to the last frame of the timeline, and then returns it to the first frame. This ensures that everything that exists on the last frame will be registered and you will be able to access it immediately following the gotoAndStop(’over’) call. The key to this approach is only items that are still in the display list on the last frame will register. If you have a blank keyframe after overClip it will not register if the last frame of the timeline is beyond that. Here is the code for this approach.

button.buttonMode = true;
button.addEventListener(MouseEvent.MOUSE_OVER, rollover);
this.addEventListener(Event.ENTER_FRAME, frameentered);
this.addEventListener(Event.RENDER, rendered);

function frameentered(e:Event) {
     this.removeEventListener(Event.ENTER_FRAME, frameentered);
     if (overClip == null) {
          stage.invalidate();
          this.gotoAndStop(totalFrames);
     }
}

function rendered(e:Event):void {
     if (this.currentFrame == this.totalFrames) {
          this.gotoAndStop(1);
     }
}

function rollover(e:MouseEvent):void {
     trace('the instance name of the overclip is: ' + this.overClip.
}

Excellent, we’re making progress. But we are working with a very simple example, a two state button (up and over). What if we had a more complex button, say 4 states (up, over, down, and disabled), and at each state different assets were added to the timeline like so.

I know, I said complex, and this timeline is far from complex, where are all the tweens, and hundreds of layers, guides, and masks. Well, lets just pretend all that fun stuff is there for now.

If we were to try the last approach with this more complex timeline we still wouldn’t have access to the clips on the over and down frames. This is because we moved the playhead from the first frame to the last frame and back to the first frame again. The playhead never touched anything in between so the clips on the over and down frames would not have been registered yet.

The first approach I came up with to register the clips on all the frames was quite innovative, so I thought. Basically I used the currentLabels array property of MovieClip. For those who don’t know, the MovieClip class now contains a currentLabel and a currentLabels property. The currentLabel returns a FrameLabel object if one exists for the current frame that the playhead is currently on. A FrameLabel Object contains two properties, name, and frame. The name property is the frame label name and the frame property is the frame number (int) of the current frame the playhead is on. The currentLabels property of MovieClip returns an array of FrameLabel Objects containing all the FrameLabels for the targeted MovieClip. Using the currentLabels array I thought I could loop through each FrameLabel in the array and move the playhead to each frame label and once it is complete return the playhead back to the first frame. However, you cannot use a traditional for loop because the frame cycle will not occur until the for loop is complete. Instead I set up a counter variable and use an EnterFrame to move through each FrameLabel Object for targeted MovieClip. This approach is very similar to our previous approach with more stops in between. Lets call this approach the milk run. Here is what my code looked like for this approach.

button.buttonMode = true;
var count:int;
if (count == 0) {
     this.visible = false;
     addEventListener(Event.ENTER_FRAME, proxyframes);
     addEventListener(Event.RENDER, rendered);
}

function proxyframes(e:Event) {
     stage.invalidate();
     gotoAndStop(this.currentLabels[count].name);
     trace(this.currentFrame);
}

function rendered(e:Event):void {
     count++
     trace('render called' + this.currentFrame)

     if (count == this.currentLabels.length) {
          init();
          gotoAndStop(1);
          removeEventListener(Event.ENTER_FRAME, proxyframes);
          removeEventListener(Event.RENDER, rendered);
     }
}

function init() {
     this.visible = true;
     button.addEventListener(MouseEvent.MOUSE_OVER, rollover);
     button.addEventListener(MouseEvent.MOUSE_OUT, rollout);
     button.addEventListener(MouseEvent.MOUSE_DOWN, press);
     button.addEventListener(MouseEvent.MOUSE_UP, release);
}

function rollover(e:MouseEvent):void {
     gotoAndStop('over');
     trace(overClip.name);
}

function rollout(e:MouseEvent):void {
     gotoAndStop('up');
}

function press(e:MouseEvent):void {
     gotoAndStop('down');
     trace(downClip.name);
}

function release(e:MouseEvent):void {
     gotoAndStop('disabled');
     trace(disabledClip.name);

     //disable buttons if there was a parent clip parent.mouseChildren = false would also work.
     button.buttonMode = false;
     button.removeEventListener(MouseEvent.MOUSE_OVER, rollover);
     button.removeEventListener(MouseEvent.MOUSE_OUT, rollout);
     button.removeEventListener(MouseEvent.MOUSE_DOWN, press);
     button.removeEventListener(MouseEvent.MOUSE_UP, release);
}

So why doesn’t this work. Well if you remember what I said earlier, moving the playhead only registers the last frame that it was on. If you run this code you will notice the rollover and rollout methods return null but the release method traces out the correct value, this is because that was the last stop on our playhead milk run.

In order for this approach to work I would have to layout my timeline appropriately. Each state clip should be on its own layer with nothing after it (including blank keyframes) on the timeline, like so:

Now you will notice that the above code works, we are now able to trace out the extra clips that appear on each frame with a label. However, now that everything exists on the last frame we really don’t need to loop through each FrameLabel to register the clips, we can just use our first trick and move the playhead to the last frame, once it renders move back to the first frame. Even though the looping through the FrameLabels code is overkill for this example I thought I would keep it in so people know that capability exists.

So we are able to access sibling displayObjects now but there still is one functional issue. Since the frames are cascading on the timeline when the playhead is on the down frame both downClip and overClip are visible. Likewise, when the playhead is on the disabled FrameLabel upClip, downClip, and disabledClip are visible. That little bug is not going to slip by the creative department. Normally we would get around this issue by adding blank keyframes on the frame following where the extra clip was displayed. If we were to do this in our example the clips wouldn’t register because they do not exist on the last frame and we would be right back where we started. There are two ways around this, one involves more code (the developer approach), the other involves more keyframes (the designer approach). Both are valid.

The timeline approach basically consists of adding keyframes (not blank keyframes) on the frame after the frame where the extra clip appears and setting the alpha of the clip on the second frame to 0. This ensures that the clip is still on the display list in the last frame and allows it to register. With this approach your timeline would now look like so:

Depending on what else is happening in your application this approach may not work. This is when you have to resort to code (yay). Basically with code we do the same thing, however, instead of simply setting the alpha to 0 we’ll set the visibility to false. This only requires three extra lines of code which are included below.

stop();

button.buttonMode = true;
var count:int;
if (count == 0) {
     this.visible = false;
     addEventListener(Event.ENTER_FRAME, proxyframes);
     addEventListener(Event.RENDER, rendered);
}

function proxyframes(e:Event) {
     stage.invalidate();
     gotoAndStop(this.currentLabels[count].name);
     //gotoAndStop(this.totalFrames);
     trace(this.currentFrame);
}

function rendered(e:Event):void {
     count++
     trace('render called' + this.currentFrame)

     if (count == this.currentLabels.length) {
          init();
          gotoAndStop(1);
          removeEventListener(Event.ENTER_FRAME, proxyframes);
          removeEventListener(Event.RENDER, rendered);
     }
}

function init() {
     this.visible = true;
     button.addEventListener(MouseEvent.MOUSE_OVER, rollover);
     button.addEventListener(MouseEvent.MOUSE_OUT, rollout);
     button.addEventListener(MouseEvent.MOUSE_DOWN, press);
     button.addEventListener(MouseEvent.MOUSE_UP, release);
}

function rollover(e:MouseEvent):void {
     gotoAndStop('over');
     trace(overClip.name);
}

function rollout(e:MouseEvent):void {
     gotoAndStop('up');
}

function press(e:MouseEvent):void {
     overClip.visible = false;
     gotoAndStop('down');
     trace(downClip.name);
}

function release(e:MouseEvent):void {
     gotoAndStop('disabled');
     trace(disabledClip.name);
     overClip.visible = false;
     downClip.visible = false;
     button.buttonMode = false;
     button.removeEventListener(MouseEvent.MOUSE_OVER, rollover);
     button.removeEventListener(MouseEvent.MOUSE_OUT, rollout);
     button.removeEventListener(MouseEvent.MOUSE_DOWN, press);
     button.removeEventListener(MouseEvent.MOUSE_UP, release);
}

That’s it. Adobe, if you are reading this, please, please, please fix this bug. It is very annoying. It is obvious that Flash Player 9 was built with Flex in mind (aka no timeline) since that is all that was available when Flash Player 9 was released. I remember reading someone from Adobe admitting that on their personal blog, if I can find the link again I will post it in the comments below. I covered a lot here, but this bug goes even further. Sometimes Event.RENDER doesn’t even fire. In another related bug stop events on the first frame of nested MovieClip do not always work. Supposidly Adobe fixed this in version 9.0.45 of the player but as you can see by the comments on Emmy’s blog posting about the 9.0.45 release it was never really fixed. In fact we ran into this issue at work recently in the latest 9.0.115 version of the player.

I actually met with the Adobe Flash Player team last week and we discussed a lot of these issues, hopefully they will take these into consideration for the next release. To a developer they may seem like minor issues but designers and animators are going to have a difficult time getting around a lot of these bugs. If anyone else has any workarounds please feel free to explain them in the comments below.

P.S. this is my longest post ever, yay me.

In AS2 there were three mouse events that I used quite often. onDragOver was thrown when the mouse button is selected and the cursor was moved over a MovieClip. This was useful to detect if another object was being dragged over a given clip. onDragOut was the opposite, this event was thrown when the mouse button was selected and the cursor was dragged outside of a given clip. And lastly, onReleaseOutside was thrown when a clip was pressed and the user dragged off the clip before releasing the mouse button. Releasing the mouse outside is a common way for a user to bail out of selecting a button among other reasons.

If you look through the AS3 documentation you will notice there are no MouseEvent.DRAG_OVER, MouseEvent.DRAG_OUT, or MouseEvent.RELEASE_OUTSIDE events. This does not mean that this functionality is not available, it’s just not as obvious. The following code shows how to recreate this functionality in AS3.

var button:Sprite = new Sprite();
button.graphics.beginFill(0x000000, 1);
button.graphics.drawRect(50,50,200,100);
addChild(button);

button.buttonMode = true;
button.addEventListener(MouseEvent.MOUSE_DOWN, buttonPress);
button.addEventListener(MouseEvent.MOUSE_UP, buttonRelease);
button.addEventListener(MouseEvent.MOUSE_OVER, buttonOver);
button.addEventListener(MouseEvent.MOUSE_OUT, buttonOut);

function buttonPress(e:MouseEvent):void {
     //the button is pressed, set a MOUSE_UP event on the button's parent stage
     //to caputre the onReleaseOutside event.
     button.parent.stage.addEventListener(MouseEvent.MOUSE_UP, buttonRelease);
}

function buttonRelease(e:MouseEvent):void {
     //remove the parent stage event listener
     button.parent.stage.removeEventListener(MouseEvent.MOUSE_UP, buttonRelease);

     //if the events currentTarget doesn't equal the button we
     //know the mouse was released outside.
     if (e.currentTarget != button) {
          trace('onReleasedOutside');
     } else {
          //the events currentTarget property equals the button instance therefore
          //the mouse was released over the button.
          trace('onRelease');
     }
}

function buttonOver(e:MouseEvent):void {
     if (e.buttonDown) {
          //if the mouse button is selected when the cursor is
          //over our button trigger the onDragOver functionality
          trace('onDragOver');
     } else {
          //if the mouse button isn't selected trigger the standard
          //onRollOver functionality
          trace('onRollOver');
     }
}

function buttonOut(e:MouseEvent):void {
     if (e.buttonDown) {
          //if the mouse button is selected when the cursor is
          //moves off of our button trigger the onDragOut functionality
          trace('onDragOut');
     } else {
          //if the mouse button isn't selected trigger the standard
          //onRollOut functionality
          trace('onRollOut');
     }
}

So let me explain what is going on here.

To recreate the onDragOver and onDragOut functionality we use the buttonDown property of the MouseEvent. The buttonDown property is a Boolean value that indicates if the users mouse button is selected when the event was triggered. If this property is true we know the user is dragging over or dragging out of the button. If the buttonDown property is false we can trigger our standard rollOver or rollOut functionality.

Recreating the onReleaseOutside functionality is a trickier multi-step process. First we must set up a MouseEvent.MOUSE_DOWN event listener on the button. Secondly we set up a MouseEvent.MOUSE_UP event listener on the same button. Third, in the MouseEvent.MOUSE_DOWN event handler we set up another MouseEvent.MOUSE_UP event listener on the button’s stage that triggers the same event handler as the MouseEvent.MOUSE_UP event handler (buttonRelease) that is set on the button. And lastly we check the currentTarget property of the event passed into the MouseEvent.MOUSE_DOWN event handler to see if it equals the button or the button’s parent stage. If the currentTarget equals the button we know the mouse button was released over the button and we can trigger the onRelease functionality. If the currentTarget property isn’t the button we can assume the user released the mouse button outside of the button. Each situation is different, button.parent.stage may not work in your application, you may have to traverse up the display list, possibly right down to the root child (the Stage) to succesfully detect the onReleaseOutside event. Below I have updated the buttonDown method to show a simple way of traversing up the display list as far as you can go and setting the MouseEvent.MOUSE_UP on the root child.

function buttonPress(e:MouseEvent):void {
     //the button is pressed, set a MOUSE_UP event on the root child in the display list
     //to caputre the onReleaseOutside event.
     var rootChild:DisplayObject = button;
     while (rootChild.parent != null) {
          rootChild = rootChild.parent;
     }
     rootChild.stage.addEventListener(MouseEvent.MOUSE_UP, buttonRelease);
}

Enjoy!

My previous team of Flash Developers at Critical Mass have recently released the start of a Flash API for the popular Google Maps API. You can read more about it here.

Currently AS2 is only supported, they mention an AS3 version will be available sometime in 2008, hopefully early 2008. I have heard from a few people that Google is about to release their own Flash API but until then we have Scott Ingalls and the entire Flash team at Critical Mass to thank.

Josh Tynjala and I were speaking today, well actually typing over IM. We were discussing a problem we both ran into recently, the fl.transitions.Tween class was not dispatching events consistently. Sometimes the TweenEvent.MOTION_FINISH event would be broadcasted, other times it would not. Sometimes our programmatic tweens were visually completing, other times they would not, in a few cases they wouldn’t even start. After a little debugging we realized that the AS3 Garbage Collection was killing the tweens. You may be asking why would the garbage collection be doing this? Let’s start out by looking at some example code.

var len:int = _model.getNumberOfItems();
for (var i:int = 0;i<len;i++)>
     var clip:MovieClip = _scope.getChildByName('button'+i);
     var myTween:Tween = new Tween(clip, 'alpha', Regular.easeOut, 0, 1, 0.5, true);
     myTween.addEventListener(TweenEvent.MOTION_FINISH, buttonAnimationComplete);
}

Pretty simple code, in my AS2 days I used code similar to this a million times and never had a problem. All I am doing is looping through a set number of MovieClips and fading them up from 0 to 100%. Most times this worked fine, but a few times the tweens never completed and buttonAnimationComplete was not called. This is because the AS3 Garbage Collection was deleting the Tweens just like it should be. What? The tweens should be deleted? Yes.

Notice that I am storing the reference to my tween in a local variable that gets overridden in every iteration of the loop. This means there is no real reference to the object and when there is no reference to an object it is flagged for removal by the garbage collector. Since there is no real way to know when the garbage collection cycle is going to run the tweens were completing some of the time and other times were being thrown into the back of the big garbage truck that slowly creeps around your code.

The best way to ensure that this doesn’t happen is to store your reference in a class level member. I ended up storing all of my tween references in a class level array. Once all of the tweens were complete I cleared the array. Here is the updated code that I used, the entire class is not shown here, trust me your scroller will thank me.

private var _buttonTweens:Array = new Array();
private var _buttonTweenCompleteCount:int;

private function animateButtons():void {
     var len:int = _model.getNumberOfItems();
     for (var i:int = 0;i<len;i++)>
          _buttonTweens.push(new Tween(clip, 'alpha', Regular.easeOut, 0, 1, 0.5, true));
          _buttonTweens[_buttonTweens.length - 1].addEventListener(TweenEvent.MOTION_FINISH, buttonAnimationComplete);
     }
}

private function buttonAnimationComplete(e:TweenEvent):void {
     buttonTweenCompleteCount++
     if (buttonTweenCompleteCount == _model.getNumberOfItems()) {
          _buttonTweens = [];
          dispatchEvent(new Event('buttonsReady'));
     }
}

I am just using Tweens as an example, but you can run into this same issue with any object that has local references that are overridden. For example, I had a Timer event set to iterate every 100 milliseconds and it called loadAsset(), a method that did just what it says, it loaded an asset.

private function loadAsset():void {
     _currentItemToLoad++;
     var myLoader:Loader = new Loader();
     myLoader.addEventListener(Event.COMPLETE, assetLoaded);
     myLoader.load(new URLRequest('images/galleryImage' + currentItemToLoad + '.jpg'));
}
private function assetLoaded(e:Event):void {
     var holder:Sprite = new Sprite();
     holder.visible = false;
     holder.addChild(e.target.content);
     _scope.addChild(holder);

     e.target.removeEventListener(Event.COMPLETE, assetLoaded);
}

Just like the Tween example above my Event.COMPLETE event was not firing all the time. This is because I was storing the reference to the Loader in a local variable that was overridden each time the loadAsset method was called. To get around this I stored all my references in a class level member (an object or an array work nicely).

Hopefully this helps you figure out why you are not seeing certain events in your code, it had me stumped for a bit. Now you know, and as G.I. Joe said “knowing is half the battle”.

First off, thank you Grant for releasing the source for your squeeze effect. Dirty code is better than no code. After looking at the source code I began to wonder how much better it would run in AS3 so I converted it. I didn’t clean up any of the code, just a straight conversion. It seems to run a little smoother. Next I am going to make it dynamic allowing you to load in an image passed in through flashVars. Maybe if I’m really bored I will set it up so you can load in multiple images from an xml file and build a gallery type app using this effect. Again, thanks Grant, as always, very cool work!


 
Download the rough AS3 source here.
 

I have seen a lot of posts lately with people asking how they can access variables and methods in an external swf that is loaded at runtime using AS3. This isn’t a difficult task, but it is much different than AS2/AS1 where you could just call directly into the loaded swf using the instance chain if you were in the same security sandbox.

First off, lets create our swf that wil be loaded in at runtime. Open up your favourite actionscript editor and create a new class with the following code.

package com.scottgmorgan {
     import flash.external.ExternalInterface;
     import flash.display.Sprite;
     public class ExternalMovie extends Sprite {
          public function ExternalMovie():void {
               //nothing in our constructor right now.
          }
          public function alert(msg:String):void {
               trace(msg);
               ExternalInterface.call('alert', msg);
          }
     }
}

Now create a new FLA and set the above class as the document class. If you are not sure how to do this simply enter the class path (com.scottgmorgan.ExternalMovie) into the document class textfield found in the property panel. Lather, rinse, repeat, compile.

Next we will create the swf that will load our ExternalMovie swf we just created. Let’s jump back to our favourite actionscript editor and create a new class with the following code.

package com.scottgmorgan {
     import flash.display.Loader;
     import flash.net.URLRequest;
     import flash.events.Event;
     import flash.display.LoaderInfo;
     import flash.display.Sprite;
     public class SourceMovie extends Sprite {
          public function SourceMovie():void {
               var loader:Loader = new Loader();
               loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoadComplete);
               loader.load(new URLRequest('ExternalMovie.swf'));
          }
          private function onLoadComplete(e:Event):void {
               var loaderInfo:LoaderInfo = e.target as LoaderInfo;
               addChild(e.target.content);
               var swf:Object = loaderInfo.content;
               swf.alert('Hello World');
          }
     }
}

That’s it, LoaderInfo saves the day. LoaderInfo.content connects you with the document class of the externally loaded swf. Lets create a new FLA, assign the SourceMovie class as the document class and compile. Make sure the SourceMovie.swf and ExternalMovie.swf are in the same directory. The as files should be in /com/scottgmorgan/. Let’s compile the SourceMovie and you should see “Hello World” in the output window if you run the swf inside the IDE, if you run it from a browser you should see an alert dialog with “Hello World”.

Another option you have is to use the ApplicationDomain class. Using the ApplicationDomain class you can add the classes from the ExternalMovie to the SourceMovie’s ApplicationDomain. This is a great way to load in code libraries at runtime.

Lets pretend we have a large application with multiple levels of security, maybe we are creating a content management system and we need multiple permission levels. User A can only update content, User B can update content and update the site map, User C is an administrator and can do everything User A and B can do but can also access tracking information, edit user profiles, update permissions, etc. When User B logs in the application loads the site map code library (sitemapadmin.swf) and adds its classes to the main ApplicationDomain. When User C logs in the sitemapadmin.swf classes would be added to the main ApplicationDomain, for this user the application would also load the trackingadmin.swf, and useradmin.swf code libraries and add all the included classes to the main ApplicationDomain.

Let’s update our SourceMovie.as file and add the ExternalMovie class to SourceMovie’s ApplicationDomain.

package com.scottgmorgan {
     import flash.display.Loader;
     import flash.net.URLRequest;
     import flash.events.Event;
     import flash.display.LoaderInfo;
     import flash.display.Sprite;
     import flash.system.ApplicationDomain;
     public class SourceMovie extends Sprite {
          public function SourceMovie():void {
               var loader:Loader = new Loader();
               loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoadComplete);
               loader.load(new URLRequest('ExternalMovie.swf'));
          }
          private function onLoadComplete(e:Event):void {
               ApplicationDomain.currentDomain.getDefinition("com.scottgmorgan.ExternalMovie");
               var myExternalMovie:ExternalMovie = ExternalMovie(e.target.content);
               myExternalMovie.alert('Hello World');
          }
     }
}

There you have it. One thing you will notice is you don’t have to add the loaded swf to the display list to access its classes. Hopefully you will be able to use these techniques in future projects.

A few months ago I blogged about a new Flash Detection JavaScript library that a friend and colleague of mine over at FeatureBlend.com created. Well, Carl has just released a new version with some pretty cool additions. Below are his words to the Flash community. Take a look and let him know what you think by leaving a comment. Hello Flash Community,

I am pleased to announce the latest updates for the JavaScript Flash Detection (Flash Detect) and JavaScript Flash HTML Generator (Flash TML) libraries.

The most notable changes are YUI namespace support (YAHOO.util.FlashDetect), JSMin version, JSLint cleanse, pattern changes and a few other goodies.

Hope you all enjoy! Carl

If you like what you see make sure to Digg these libraries.
Digg the JavaScript Flash Detection Library (Flash Detect)
Digg the JavaScript Flash HTML Generator Library (Flash TML)

Well this will probably be my last posting that announces something my team (Yahoo! Flash Platform) at Yahoo has launched. From now on it will be my former team as I am leaving to work for Disney on Friday. This week we launched three very exciting things. The new Yahoo! Flash Platform blog (http://www.yswfblog.com), the new and improved Yahoo! Flash Development Center (http://developer.yahoo.com/flash) and our new Astra Flash Components. The Astra Library is our collection of Flash and Flex components, libraries and toolkits. In this inaugural release, ASTRA contains five UI components that complement the existing set provided with Flash CS3. The new UI components are: Tree, Menu, TabBar, AutoComplete, and Charts. ASTRA is open-source under the BSD license, and follows an approach similar to the very popular YUI Library. Keep an eye on the ASTRA library, I know first hand that there are some very cool things to come.

Well, pulled a few more hairs out today. Hard to believe I have any left. I am working on a multi-lingual application that needs embeded fonts. As most of you know, loading every character in a given fontset equates to one big ass swf. For example, if you want to embed a regular and bold Japanese font you’re looking at approx. 13 megs, and depending on the quality of the font you could be close to 20 megs. That’s a lot of font. In an ideal world the Flash Player would allow for dynamic runtime shared font libraries that allow for only a subset of characters to be embeded.

In AS2 there were a few ways (hacks) to load font libraries at runtime. Unfortunately none of the AS2 hacks work in Flash CS3 using AS3. However, there is an answer. In Flex you can embed fonts at compile time using the [Embed] metadata tag in your Actionscript. And the best part is you can use the unicodeRange attribute to define a subset of characters you want to embed. Below is a class I created that compiles a swf that contains all Latin I characters in the Arial font.

package {
     import flash.display.Sprite;
     public class _Arial extends Sprite {

          [Embed(source='C:/WINDOWS/Fonts/ARIAL.TTF', fontName='_Arial', unicodeRange='U+0020-U+002F,U+0030-U+0039,U+003A-U+0040,U+0041-U+005A,U+005B-U+0060,U+0061-U+007A,U+007B-U+007E')]

          public static var _Arial:Class;

     }
}

A couple things to point out here. The fontName attribute value can not be the same name as a device font. If I were to change my code to use fontName=’Arial’ the compiler throws the following warning “the embedded font ‘Arial’ may shadow a device font of the same name. Use fontName to alias the font to a different name”. To get around this I simply added an underscore before the name. From this point on you must reference the _Arial in your TextFormats or CSS. Now if you compile that it will create a swf named _Arial.swf.

Ok, great, now what? Well, now you have to load the font into the application. Here is a sample class that loads in the font and displays some rotated text just to prove that it is embeded.

package {
     import flash.display.Loader;
     import flash.display.Sprite;
     import flash.events.Event;
     import flash.net.URLRequest;
     import flash.text.*;

     public class FontLoader extends Sprite {

          public function FontLoader() {
               loadFont("_Arial.swf");
          }

          private function loadFont(url:String):void {
               var loader:Loader = new Loader();
               loader.contentLoaderInfo.addEventListener(Event.COMPLETE, fontLoaded);
               loader.load(new URLRequest(url));
          }

          private function fontLoaded(event:Event):void {
               var FontLibrary:Class = event.target.applicationDomain.getDefinition("_Arial") as Class;
               Font.registerFont(FontLibrary._Arial);
               drawText();
          }

          public function drawText():void {
               var tf:TextField = new TextField();
               tf.defaultTextFormat = new TextFormat("_Arial", 16, 0);
               tf.embedFonts = true;
               tf.antiAliasType = AntiAliasType.ADVANCED;
               tf.autoSize = TextFieldAutoSize.LEFT;
               tf.border = true;
               tf.text = "Scott was here\nScott was here too\nblah scott...:;*&^% ";
               tf.rotation = 15;

               addChild(tf);
          }
     }
}

And there you have it. Run time embeded fonts. The key lines to look at here are

var FontLibrary:Class = event.target.applicationDomain.getDefinition("_Arial") as Class;

The getDefinition method returns a reference to the _Arial class loaded in through the swf (event.target). The next line:

Font.registerFont(FontLibrary._Arial);

registers the loaded in _Arial font in the global font list. If you were to trace out the results of Font.enumerateFonts() you will now see _Arial at the top of the list.

Now lets say your site was in a few different languages you may add the following line to the FontLoader constructor to load language specific fonts at runtime.

public function FontLoader() {
     var langManager:LanguageManager = LanguageManager.getInstance();
     var langID:String = langManager.getLanguageCode(); //returns jp
     loadFont(langID + "_Arial.swf");
}

Instead of loading in _Arial.swf the application will load in jp_Arial.swf. jp_Arial.swf would be another generated font swf like the _Arial example above but this time the unicodeRange would only include the Japanese fonts you need. All you have to do now is create a CSS file and fonts for each language, store the proper language code somewhere within the application and use that language code when loading your CSS files and fonts.

You may not know this but Adobe has supplied us with sample unicodeRanges in the following file “\Applications\Adobe\Flex Builder 2\Flex SDK 2\frameworks\flash-unicode-table.xml”. You can either use one of the supplied ranges or create your own. You may only need to embed a few characters, if so just list the unicode values of the characters you want each seperated by a comma.

Sounds pretty straight forward eh? Or is it????

In doing this I ran into a huge bug using Flex Builder to generate the font swfs. When I was experimenting with the unicodeRanges my compiled versions did not contain the proper character ranges that I specified. For example I would define a range that only contained Uppercase characters. In my test font loading app I would only see numbers. Only if I removed the unicodeRange attribute would I see all my characters. This led me to believe that Adobe had documented something that really wasn’t part of the compiler. I tried deleting files, I was checking timestamps, nothing. Then I tried to clean my project before compiling (In FlexBuilder select Project > Clean). It worked! The subset of characters I defined in my unicodeRange only loaded into my test app. YAY! Then I tried switching the unicodeRange again. DOH! nothing. Cleaned project again and BINGO!

Lesson learned: Whenever you change your unicodeRange clean your project before you compile or else your change may not be compiled properly.

I haven’t tried this in Flex 3 yet to see if the bug has been addressed. I did look at the Flex 3 bug system and didn’t see it listed. Either it has been fixed or no one has run into this issue yet. It is pretty obscure I guess.

I have had many people request that I post the code to my ExternalInterfaceBuffer class that I made reference to in my ExternalInterface…HELP posting. This code is part of the Yahoo Maps AS3 Communication Kit that I wrote and can be downloaded from the Yahoo! Flash Developer Network. As a bonus you will also get some other very cool Yahoo! AS3 API wrappers, Search, Answers, Weather, and Upcoming.org. To make a long story short, the issue I noticed was some of my ExternalInterface calls were getting dropped. I did a boat load of debugging, and discovered it was actually the browser script engines overloading and dropping the calls. So I wrote a singleton class that queues up all the ExternalInterface calls and sends them one at a time in 50 second intervals. One thing I noticed, 50 second intervals works fine with Actionscript 3, Actionscript 2 is a lot slower so I had to slow it down to 100 miliseconds or else I was running into the same issues, just not as often.

/*
Copyright (c) 2007, Yahoo! Inc. All rights reserved.
Code licensed under the BSD License:
http://developer.yahoo.net/yui/license.txt
version: 2.2.0
*/
package com.yahoo.webapis.maps.utils {

     import flash.external.ExternalInterface;
     import flash.utils.setInterval;
     import flash.utils.clearInterval;

     /**
      * Utility to buffer outgoing ExternalInterface calls. Simoltaneous
      * calls get dropped if they aren't buffered. It seems both Firefox and
      * IE's script engine gets overloaded and either drops calls or
      * throws a javascript error. This buffer ensures the script engines
      * only get one call at a time.
      *
      * @langversion ActionScript 3.0
      * @playerversion Flash 9
      * @author Scott Morgan 02/25/2007
      *
      * @see flash.external.ExternalInterface
     */
     public class ExternalInterfaceBuffer {

          /**
           * @private
           * A singleton class that is accessed by almost all classes in this
           * communication kit. This static variable gives access to this class
           * ensuring there is only ever one instance of it.
           *
           */
          private static var instance:ExternalInterfaceBuffer = new ExternalInterfaceBuffer();

          /**
           * @private
           * An array of methods waiting in the queue to be called. Once they
           * are called they are removed from the array.
           *
           */
          private var methodQueue:Array = new Array();

          /**
           * @private
           * Access to the interval that runs while the methods are being called.
           * When all methods in the queue are called this variable is used to
           * clear the interval.
           *
           */
          private var methodCallInterval:Number;

          /**
           * Constructor
           * If an instance of this class already exists an error message is thrown
           * informing that this class should only be accessed through the getInstance()
           * method.
           *
           * @see com.yahoo.webapis.maps.utils.ExternalInterface.getInstance()
           */
          public function ExternalInterfaceBuffer() {
               if( instance ) throw new Error( "Singleton and can only be accessed through Singleton.getInstance()" );
          }

          /**
           * @public
           * This static method is what makes this class a singleton, it ensures that only
           * one instance of this class is instantiated.
           *
           */
          public static function getInstance():ExternalInterfaceBuffer {
               return instance;
          }

          /**
           * @public
           * Method used to add calls to the methodQueue array. If the length of the methodQueue
           * array is greater than 0 the methodChurn interval is kicked off and runs every 50
           * milliseconds until the methodQueue's length is 0 at which point the interval is
           * cleared.
           *
           * @param obj and object containing a method string, and a data
           * object. The method string is the method the ExternalInterface will
           * call and the data object is the object that will be passed to the
           * calling method as an argument.
           */
          public function addCall(obj:Object):void {
            methodQueue.push(obj);
            if (isNaN(methodCallInterval) || methodCallInterval == 0) {
                methodCallInterval = setInterval(methodChurn, 50);
            }
          }

          /**
           * @private
           * This method is called every 50 milliseconds until the methodQueue array is empty.
           * This method sends the first method listed in the methodQueue array to the
           * ExternalInterface.call method.
           *
           * @see flash.external.ExternalInterface.call
           */
          private function methodChurn():void {
            if (methodQueue[0].method != undefined && methodQueue[0].method != null) {
                ExternalInterface.call(methodQueue[0].method, methodQueue[0].data);
            }
            methodQueue.shift();
            if (methodQueue.length == 0) {
                clearInterval(methodCallInterval);
                methodCallInterval = undefined;
            }
          }
     }
}

Pretty straightforward stuff. Ok, that’s great, now how do you use it. Simple, one line of code, well two because you have to instantiate the class.

First you have to remember to import the class

import com.yahoo.webapis.maps.utils.ExternalInterfaceBuffer

Then you have to instantiate, you may choose to write one line of code to instantiate the class and call the addCall method in one line. I was used this class multiple times in the class so I instantiated it once and made reference to it with a variable scoped to the class.

private var EIBuffer:ExternalInterfaceBuffer = ExternalInterfaceBuffer.getInstance();

And lastly, add your call to the queue by calling the public addCall method. This method expects an object containing a String containing the method name and an encapsulated data object that contains the arguments you want to pass along to the method being called via the ExternalInterface.

EIBuffer.addCall({method:"myJavascriptMethod", data:{argA:'blah', argB:'merp', argC:'DOH'}});

That’s it, I know I could of made it a bit more elegant but it does everything I needed at the time. Plus it’s open source, now the world can all join in and make it perfect :)

Enjoy!