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

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.

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.

Custom UI for Flash Components – Part 4 – Prepping your UI

Part 1|Part 2|Part 3|Part 4
Flex Project File
UPDATE: You will also have to implement one last step: bypassing the flex preloader in order to make this work.

Now that our component is ready, we use the TextField in out Flex project to indicate the current state of the Flash component.

As far as I know, there’s no way of determining which variables are Inspectable in our MyComponent class. This means we have to hard code the resulting variables in Flex as well.

Update our flex Application to the following:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
				layout="absolute"
				creationComplete="init()">
	<mx:Script>
		<![CDATA[
			import adobe.utils.MMExecute;
 
			public function init():void
			{
				buttonLabel.text = MMExecute("fl.getDocumentDOM().selection[0].parameters['buttonLabel'].value");
			}
 
			public function updateParams():void
			{
				MMExecute("fl.getDocumentDOM().selection[0].parameters['buttonLabel'].value = '" + buttonLabel.text + "'");
			}
		]]>
	</mx:Script>
 
	<mx:TextInput id="buttonLabel"
				  change="updateParams()"/>
</mx:Application>

First, we call init() when the Application is done instantiating.
This uses JSFL to retrieve the “buttonLabel” parameter of the selected item on the stage. This will always be your component because it will only show this UI when the component (and only this component) is selected.
It then sets the text attribute of the TextInput to the buttonLabel parameter.
Finally, we’ve set a listener to the change Event of the buttonLabel TextInput that calls the upddateParams() function.
The updateParams sets the buttonLabel parameter of the selected item in Flash to the value of the text attribute of the buttonLabel TextField. This update will happen every time you change the textField, so it will in effect be realtime.

Build your Project again by choosing Build Project from the Project Menu and you’re done. When you drag an instance of your component onto the Stage, the Component Inspector should begin with the default text and if you change it, it should then stick.

UPDATE: You will also have to implement one last step: bypassing the flex preloader in order to make this work.
Part 1|Part 2|Part 3|Part 4
Flex Project File

JSFL 3: Document Path and URI

In JSFL, there’s two different reference systems – the directory structure and the URI structure. In the documentation, if you want the currently open file’s location they say you can reference

fl.getDocumentDOM().path

this gives you the directory structure. There, however, is no mention of how to get the URI. Some people have built clever find and replace scripts, but there’s an undocumented attribute called

fl.getDocumentDOM().pathURI

that gives you the URI as needed. Hope this helps someone!

JSFL 2: Making a Panel

Making a panel in Flash is rather easy, once you find the little bit of information you need. Panels in Flash are just swfs that happen to be in a specific directory.

Windows® 2000 or Windows® XP:
boot drive\Documents and Settings\user\Local Settings\Application Data\Adobe\Flash CS3\language\Configuration\WindowSWF

Mac OS® X:
Macintosh HD/Users/userName/Library/Application Support/Adobe/Flash CS3/language/Configuration/WindowSWF

drop your swf in there, and it should be available in the Windows>Other Panels menu.

JSFL 1: Hello World

I’m embarking on learning JSFL, and if you’re looking to learn it, too, maybe my path will help you.

First thing’s first: A hello world example:
create a new, empty file on your computer called helloworld.jsfl and put this in it.

alert("hello world");

Open the Flash IDE and choose Run Command from the Commands menu. Point to your new JSFL file and viola, an alert, as expected.