Flash IDE / AS3 [object global] Inside of Dynamic Function

I ran across an odd situation inside the Flash IDE where I was trying to trace out parameters about the timeline’s class.

Here’s the code I was running:

import flash.events.*
this.addEventListener(Event.ENTER_FRAME, function(e:Event){trace(this)});

the result looked something like this:

[object global]
[object global]
[object global]

This is a result of the dynamically created function created in the event listener. The function is created in some alternate universe called “global” and has nothing to do with the scope of the stage onto which it is placed. The correct code should be:

import flash.events.*
this.addEventListener(Event.ENTER_FRAME, handleFrame);
function handleFrame(e:Event):void
{
	trace(this);
}

This will produce something like:

[object MyClass]
[object MyClass]
[object MyClass]

Cannot Trace using Debugger on OSX

Oh good lord, all I wanted to do was trace the output of my flash debug player, yet there was nothing in the usual location. Typically I just run

tail -f ~/Library/Preferences/Macromedia/Flash\ Player/Logs/flashlog.txt

in the Terminal and it spits out everything Flash does. The file didn’t exist on my system, so I couldn’t tail it. The solution turned out to be a missing mm.cfg file.
in ~/Library/Application\ Support/, produce a mm.cfg file with the following content:

TraceOutputFileEnable=1
ErrorReportingEnable=1

This, along with a correct debugger, should produce traces.

ExternalInterface Callbacks not Firing in Firefox when Using SWFObject

Your externalinterface call works fine in chrome, safari, maybe even IE. That’s because it takes a second for Firefox to get with the program and actually shove your object into the element.

here’s a happy little hack to make things work again:
Flash:

import flash.external.ExternalInterface;
ExternalInterface.addCallback("myFunction", myFunction);
function myFunction():void
{
	trace("JS Callback Successful");
}

Javascript:

//the swf id is the object into which SWFobject has loaded your SWF file
var swf= document.getElementById("my_swf_id");
try {
	swf.myFunction();
}
catch (e) { 
	//swf is not loaded yet, wait a bit and try again
	setTimeout(function(){
		swf.myFunction();
	}, 10);
}

JSFL Changes to selection Array Don’t Prompt for Save

When you use JSFL to do something to the selection array, such as

MMExecute("fl.getDocumentDOM().selection[0].x += 50;");

If that’s all you do, saving flash then closing the file won’t keep your changes.
However, modifying objects on stage using things other than selection can provoke a change.

MMExecute("fl.getDocumentDOM().moveSelectionBy({x:0, y:0})");

Is an excellent workaround originally posted at kirupa

CS5 Classic Dynamic TextField Font Embedding

Here’s the concept:
In a file that’s publishing to Flash 9, create a textfield, give it an instance name of “tf1”, set the font to comic sans, and make it dynamic.
In the actionscript, do the following:

tf1.text = "this text is normal";
tf1.rotation = 30;

when you compile, you should get an error like
Fonts should be embedded for any text that may be edited at runtime, other than text with the "Use Device Fonts" setting. Use the Text > Font Embedding command to embed fonts.
This is solved by embedding the fonts, but when you’ve got one font with multiple states, things get a little weird.
Duplicate your textfield and set the instance name to tf2
Set the font to comic sans bold
Click the Embed button. Comic Sans Bold will be added. Choose the typical settings for embedding, Upper, Lower, Numbers, Punctuation and hit ok.
Click your other textfield and hit Embed. The second font, Comic Sans Regular will be added. Choose the typical settings for embedding, Upper, Lower, Numbers, Punctuation and hit ok.
Set your actionscript to

tf1.text = "this text is normal";
tf1.rotation = 30;
tf2.text = "this text is bold";
tf2.rotation = 30;

An odd thing happens. Whichever font we embedded first seems to be the only one that’s used. This is fixed in one of two ways:

Okay way:
Use htmlText with italic and bold indicators to produce the effect you want.

tf1.text = "this text is normal";
tf1.rotation = 30;
tf2.htmlText = "<b>this text is bold</b>";
tf2.rotation = 30;

Better way:
use getTextFormat and setTextFormat

var format:TextFormat = tf1.getTextFormat();
tf1.text = "this text is normal";
tf1.setTextFormat(format);
tf1.rotation = 30;
format = tf2.getTextFormat();
tf2.text = "this text is bold";
tf2.setTextFormat(format);
tf2.rotation = 30;

this can also be done more dynamically using functions:

function setText(txt, val)
{
	var format:TextFormat = txt.getTextFormat();
	txt.text = val;
	txt.setTextFormat(format);
}
setText(tf1, "this text is normal");
setText(tf2, "this text is bold");
tf1.rotation=30;
tf2.rotation=30;

cs5_font_embedding.fla

fl.browseForFileURL won’t work across hard drives

In JSFL, when you use fl.browseForFileURL it returns a URI pointing to the file or folder you’ve chosen. On OX, however, the reference is wrong when you choose a file on a hard drive other than your primary.

Example: I have a hard drive on my mac named HD2. If I browse to a file on there, the URI returned will be

file:///HD2/myFile.txt

If you then immediately try a

FLfile.exists('file:///HD2/myFile.txt')

it returns false. This is due to OSX’s referencing system. the correct location is

file:///Volumes/HD2/myFile.txt

so all we have to do is add the word “Volumes/” to the string and the reference will be correct.

public static function browseForFileURI( title:String = "", type:String = "open" ):String
{
   var ar:Array = MMExecute( "fl.browseForFileURL('" + type + "','" + title + "');" ).split("/");
   ar[2] = "/Volumes";
   return ar.join("/");
}

Helpful JSFL Scripts for Indexing the Flash IDE Stage

Get all of the layers in a timeline

By using JSFL, you can use this code to get a list of all of the layers in a timeline. This is returned as an array of named objects whose data is accessible like this:

var layers = getLayers(fl.getDocumentDOM().getTimeline());
layers[0].name

This will return the first layer’s name.

var getLayers = function(timeline){
   var layers = [];
   for(var l in timeline.layers){
      layers.push({
         name:timeline.layers[l].name,
         layer:timeline.layers[l],
         index:l
      });
   }
   return layers;
};

Get all of the keyframes in a layer

By using JSFL, you can use this code to get a list of all of the Keyframes on a layer. This is returned as an array of named objects whose data is accessible like this:

var keyframes = getKeyframes(fl.getDocumentDOM().getTimeline().layers[0]);
keyframes[0].index;

This will return the first keyframe’s index, allowing you to reference it later using layer.frames[index].

var getKeyframes = function(layer){
   var keyframes = [];
   for(var f in layer.frames){
      if (f==layer.frames[f].startFrame){
         keyframes.push({
            frame:layer.frames[f],
            index:f
         });
      }
   }
   return keyframes;
};

Get all of the instances in a keyframe

By using JSFL, you can use this code to get a list of all of the instances on a keyframe. This is returned as an array of named objects whose data is accessible like this:

var instances = getInstances(fl.getDocumentDOM().getTimeline().layers[0].frames[0]);
instances[0].index;

This will return the first instance’s index, allowing you to reference it later using frame.instances[index].

var getInstances = function(keyframe){
   var instances = [];
   for(var e in keyframe.elements){
      if(keyframe.elements[e].elementType == 'instance'){
         if(keyframe.elements[e].libraryItem.itemType != 'compiled clip'){
            instances.push({
               instance:keyframe.elements[e], 
               index:e
            });
         }
      }
   }
   return instances;
};

JSFL and compiled clips

I’ve run into a number of issues trying to reconcile JSFL and compiled clips. Some of the features of a library object are unavailable when inspecting a compiled clip, leading to the wonderful JavaScript Errors we’ve come to love.

The particular oddity I’ve been wrestling with is that CompiledClipInstance objects don’t have a timeline, so if I try and walk the display list, digging into items as I find them, when I hit one of these, the reference to instance.timeline throws an error. The only solution is to exclude these kinds of items from your search.

Flash IDE CS4 crashes on any AS3 file close after profiling in Eclipse FlexBuilder 3

Recently I felt the need to work with the Flex Builder 3 Profiler. I found, as many OSX people do that the profiler didn’t work out of the box. I modified my mm.cfg file with the following line

PreloadSwf=/Users/mengel/Documents/workspace/.metadata/.plugins/
	com.adobe.flash.profiler/ProfilerAgent.swf?host=127.0.0.1&port=9999

This allowed the profiler to talk to pretty much every swf I load and was working great. What I didn’t know is that when you get your profiler working, it starts modifying your Flash Player preferences in a way that the Flash IDE may not like. It specifically added a folder:

/Users/mengel/Library/Preferences/Macromedia/Flash Player/#Security

This folder, coupled with my project settings, produced a configuration file that, every time I closed anything remotely AS3 related, crashed Flash, never to return. Since this is a bug in Flash player, uninstalling/reinstalling CS4 does nothing.
Even opening the projects panel and closing it breaks Flash as the IDE uses the player to display it. The solution is to remove the #security folder, drop your mm.cfg, and work without the profiler until you absolutely need it.

A friend of mine also tells me that the beta of FlashBuilder 4 may solve this problem as well. Your mileage may vary.

Adding a classpath to the Flash IDE using JSFL

MXP files can place classes most anywhere on the user’s computer, but in order to make Flash able to use them, you need to add the classpath to either the FLA or to the IDE. The IDE is preferable, in my opinion, since you only have to add it once and it works for all files. This is how you do that using JSFL.

MMExecute(
	"var found = false;" +
	"var paths = fl.as3PackagePaths.split(';');" +
	"for(var i = 0; i<paths.length; i++){" +
	"	if(paths[i] == '$(LocalData)/myClasses'){" +
	"		found=true;" +
	"	}" +
	"}" +
	"if(!found){" +
	"	paths.push('$(LocalData)/myClasses');" +
	"	fl.as3PackagePaths = paths.join(';');" +
	"}" );

What are we doing?

  • First, we set a variable to determine if we already have this classpath in our list (multiple classpaths of the same location can make Flash crash instantly)
  • Next, we take the semicolon-delimited string from fl.as3PackagePaths and assign it to an array variable.
  • Then we loop through this array looking for a match for our classpath. In my case, I’ve chosen to use a classpath variable, $(LocalData) that resolves to your Flash Configuration folder (Mac is /Users/UserName/Library/Application Support/Adobe/Flash CS4/en/Configuration)
  • If we find it, we set our found variable to true to indicate we shouldn’t add the path again.
  • Otherwise, we add the path to the array, convert it back to a string, and set the Flash IDE’s paths equal to it.