9 October 2006
No prior understanding of JSFL is required. However, you should be familiar with the Flash authoring environment and how to use invisible buttons in your movies.
Intermediate
One of the many nice things about the Flash authoring environment is its extensibility. If there is something missing that would really make your development easier and less tedious, you can extend the authoring environment to include almost anything you imagine by creating new panels, components, tools, behaviors, and commands.
One of the simplest ways to extend Flash is by writing scripts using JavaScript Flash (JSFL). JSFL commands are similar to how you might extend other applications with macros. They are external scripts stored as a text file and interpreted by Flash at design-time when you run them.
In this tutorial, you will create a JSFL command, a user interface to the command, and a cross-platform package so that you can distribute the command for others to install.
All banner ads have one thing in common: When you click the ad, you go to another website. Flash banners are no exception. Some of the cooler Flash ads let you interact with them briefly before carting you off to their site. The majority of ads, however, simply accept a user click anywhere on the ad. The industry standard way of doing this is by creating an "invisible button," which is simply a button that has only a hit state and cannot be seen. This button is usually the same size of the Stage, is placed on the bottommost layer of your FLA file, and handles mouse down events not captured by buttons lying on top of it.
Having created a lot of Flash banner ads in my time, I grew tired of adding invisible buttons to each project I worked on. Then it dawned on me: Wouldn't it be cool if there was a command to do this for me?
Let's sum up the feature list for this new command:
Now that you know what you are building, you can break it down into a series of steps that you'll build upon.
Create a folder on your desktop named My Commands. This folder can be anywhere, really, but it'll be easier to follow if we have it in the same place. Open Flash, select File > New, and choose Flash JavaScript File in the New Document dialog box. Save this file to your My Commands folder as Invisible Button.jsfl, but don't close it just yet because we'll be writing to it.
Note: Double-clicking a JSFL file does not open the script for editing in Flash. Instead, the Flash authoring environment tries to run the command. To edit a JSFL file, you need to open it from within Flash.
After you save your document, add some test code to make sure things are running properly. Type the following code into your "Invisible Button.jsfl" document:
var flash_doc = fl.getDocumentDOM(); // Document object reference
var flash_lib = flash_doc.library; // Library object reference
var flash_tl = flash_doc.getTimeline(); // Timeline object reference
var flash_w = flash_doc.width; // Document width
var flash_h = flash_doc.height; // Document height
fl.trace("flash_doc: " + flash_doc);
fl.trace("flash_lib: " + flash_lib);
fl.trace("flash_tl: " + flash_tl);
fl.trace("flash_w: " + flash_w);
fl.trace("flash_h: " + flash_h);
You'll notice that I didn't use strict typing for my variables. This is because we are coding with JavaScript. It's easy to forget this and think you are coding with ActionScript because of the similar syntax and ActionScript-like methods such as trace().
Now save your document and create a new Flash document by choosing File > New > Flash Document. If your defaults are unchanged, you should now have an empty movie that is 550 × 400 pixels in size.
Let's test our new command and see how it works. With the empty Flash document in focus, run the command by choosing Commands > Run Command. You will be presented with an Open File dialog box. Simply browse for and select the "Invisible Button.jsfl" file you just created and click Open. If the command executes properly, your Output window should open and trace five lines from the script, as follows:
flash_doc: [object Document]
flash_lib: [object Library]
flash_tl: [object Timeline]
flash_w: 550
flash_h: 400
Start adding some features from our list. Skip the dialog box for now and begin with the invisible button. Add a Button item to the Library with the name invisible:
var symbolname = "invisible";
flash_lib.addNewItem("button", symbolname);
The addNewItem(type [,namePath]) method of the Library object enables you to add any new type to the Library, such as button or movie clip, and assign it a name. The first parameter is the type—in our case, "button". The second parameter is the name for our symbol; name it "invisible".
Save the JSFL document and open the empty FLA file you have been using for testing. Run the command the same way we did earlier; you will see a new button symbol added to your Library.
Overachievers who clicked the command twice may have noticed a slight bug. Running the command a second time gives you an error message: "The name invisible is already taken. Please use a different name." There are a number of ways to solve this issue. I'm going to assume a few things:
Your assumptions may be different than mine so you may choose to loop through incremented names until you find one that's not already taken. For the sake of this tutorial, but mostly because I'm the one writing it, we'll go with my assumptions and choose to delete the existing version of the invisible button. Here's how:
var symbolname = "invisible";
// Check the library to see if the symbol already exists
// if it does delete it
while (flash_lib.itemExists(symbolname))
{
flash_lib.deleteItem(symbolname);
}
// Add the new symbol to the library
flash_lib.addNewItem("button", symbolname);
Simple enough, right? We now have the button symbol in the Library and correctly named, so we can scratch the first item off our feature list.
We now have an empty button. What you need to do next is add a rectangle shape to its Hit Area frame. The way to do this is by entering Edit mode, draw on the Stage, and exit Editing mode.
To edit a symbol in the Library, call the editItem([ namePath ]) method of the Library object and pass it the name of the symbol you wish to edit:
flash_lib.editItem(symbolname);
Doing this has pretty much the same effect as if you were to double-click the symbol in the Library and open it for editing. The exitEditMode() method of the Library object closes the symbol and returns you to the Stage. Wait until after you draw to implement the Exit method.
Now you need to implement some drawing while you have the symbol in Edit mode. To do this, obtain a reference to the current Timeline. Keep in mind that the reference you already stored as flash_tl is a reference to _root's timeline. Once you enter Edit mode on a symbol, the timeline changes to that of the symbol. To establish a reference to the symbol's timeline, grab it the same way you did for _root:
button_tl = flash_doc.getTimeline();
This can become confusing because it's the exact same call. It's important that you understand and pay attention to the context of this call. Know which timeline you are viewing when you grab a reference to it. Because the previous line of code was a call to editItem, you know which timeline you are viewing.
You want to edit the Hit Area frame of the button, so you need to have a keyframe on frame 3:
button_tl.insertKeyframe(3);
button_tl.currentFrame = 3;
Note: Frames are zero-based, so the frame at index 3 is actually the fourth frame. A button's four frames are labeled Up, Over, Down, and Hit—in that order.
By setting the Timeline object's currentFrame property to 3, you move the playhead to that frame. Any drawing you do will be on the Stage at this frame. However, the drawing isn't done on the Timeline's object reference; it's done on the document's object reference:
flash_doc.addNewRectangle({left:0, top:0, right:flash_w, bottom:flash_h}, 0);
flash_doc.selectAll();
flash_doc.breakApart();
flash_doc.setFillColor("#000000");
flash_doc.setStroke("#00000000", 1, "solid");
flash_doc.selectNone();
You've successfully drawn a rectangle on the Hit Area frame of your invisible button symbol. Now you can exit Editing mode and scratch off feature #2 from the list:
flash_doc.exitEditMode();
The invisible button is now complete so you can add it to the Stage on the timeline of _root (or _level0). Do this by calling the addNewLayer([name] [,layerType [,bAddAbove]]) method of your flash_tl timeline object reference. This method returns an ID which you will use to select the layer:
// Create a new layer for this symbol and rename it to button
var layerNum = flash_tl.addNewLayer();
flash_tl.setSelectedLayers(layerNum, true);
flash_tl.setLayerProperty("name", "button");
The setLayerProperty(property, value [,layersToChange]) method of the Timeline object enables you to set the name of the layer to button.
Now that you have the layer both named and selected, you can add symbol instances to it:
// Place the button on the stage and add the actionscript
flash_lib.addItemToDocument({x:flash_w/2, y:flash_h/2}, symbolname);
Using the Library object reference, place the invisible button symbol on the Stage using the addItemToDocument(position [,namePath]) method. By adding an item to the document, it automatically becomes selected. Since you know it's the only thing selected, you can simply reference index zero of the selection array.
Next, add ActionScript to the button click. (_root.clickTag is a variable that most media placement companies like DoubleClick use for passing URLs into the SWF. Some companies use _root.clickTAG, so be sure to get the proper case from your vendor, because case matters.) These values are hard-coded for now but will be part of the Options dialog box in the Phase 2 of this tutorial:
flash_doc.selection[0].actionScript = "on (release) {\n\tgetURL(_root.clickTag, \"_blank\");\n}";
flash_doc.selectNone();
Notice the backslashes (\) in the above script. They escape the character that follows them, which tells the JSFL interpreter to treat the next character in a special way. In this example, \n tells the interpreter you want a "new line", \t means you want a tab character to indent the new line, and \" means you want a quote mark. (If you don't escape the quote, the interpreter might think you are tring to end the string!) When the command is run and the ActionScript is placed in the movie, the final output looks like this:
on (release) {
getURL(_root.clickTag, "_blank");
}
You've now completed features #3, #4, and #5 on the list. Save and run your command to see it in action!
Here is the current state of the JSFL code in its entirety. This code reflects the items I've discussed up to this point and will change as you continue this tutorial:
/**
* Invisible Button.jsfl (phase 1 of 3)
* JSFL Command that adds an invisible button to the library of an open
* FLA and places that button on to the current timeline at the height
* and width of the stage.
*
* Author: James O'Reilly - JOR
* www.jamesor.com
*
* This work is licensed under the Creative Commons attribution 2.5
* This comment block must remain intact.
*/
var flash_doc = fl.getDocumentDOM();
var flash_lib = flash_doc.library;
var flash_tl = flash_doc.getTimeline();
var flash_w = flash_doc.width;
var flash_h = flash_doc.height;
var symbolname = "invisible";
// Check the library to see if the symbol already exists
// if it does delete it
while (flash_lib.itemExists(symbolname))
{
flash_lib.deleteItem(symbolname);
}
// Add the new symbol to the library
flash_lib.addNewItem("button", symbolname);
flash_lib.editItem(symbolname);
// Draw the button
button_tl = flash_doc.getTimeline();
button_tl.insertKeyframe(3);
button_tl.currentFrame = 3;
flash_doc.addNewRectangle({left:0, top:0, right:flash_w, bottom:flash_h}, 0);
flash_doc.selectAll();
flash_doc.breakApart();
flash_doc.setFillColor("#000000");
flash_doc.setStroke("#00000000", 1, "solid");
flash_doc.selectNone();
// Exit edit mode and add the symbol to the stage
flash_doc.exitEditMode();
// Create a new layer for this symbol and rename it to button
var layerNum = flash_tl.addNewLayer();
flash_tl.setSelectedLayers(layerNum, true);
flash_tl.setLayerProperty("name", "button");
// Place the button on the stage and add the actionscript
flash_lib.addItemToDocument({x:flash_w/2, y:flash_h/2}, symbolname);
flash_doc.selection[0].actionScript = "on (release) {\n\tgetURL(_root.clickTag, \"_blank\");\n}";
flash_doc.selectNone();
In this section I discuss creating a dialog box using XML and associating it with the invisible button command.
This dialog box will show a list of options that the developer (or just you) can customize when using the new command. Here are the options to implement:
In your My Commands folder, or whatever folder contains the "Invisible Button.jsfl" file, create an empty text document named Invisible Button.xml. We'll keep the name the same as the command—except for the extension—in this tutorial, but this doesn't need to be the case. You can name this file anything you want. You can even use multiple XML files per command in case you want to make a complex interface. The XML is parsed with an XML-to-UI engine that uses a subset of the XML User Interface Language (XUL) plus some additional Flash-only tags. You can see the full documentation in the Flash LiveDocs.
Open this empty XML document in the text editor of your choice so you can add the following content to it:
<?xml version="1.0"?>
<dialog title="Invisible Button" buttons="accept,cancel">
</dialog>
Here we define the dialog box as having the title "Invisible Button" and adding OK and Cancel buttons to the form. Because you'll want to tweak your form and see how it looks as you build it, open it from within your command:
var flash_doc = fl.getDocumentDOM();
var flash_lib = flash_doc.library;
var flash_tl = flash_doc.getTimeline();
var flash_w = flash_doc.width;
var flash_h = flash_doc.height;
// Be sure to set this path to match your setup
var result = flash_doc.xmlPanel("file:///C:/Documents and Settings/Username/Desktop/My Commands/Invisible Button.xml");
// rest of the code follows
By using the xmlPanel(fileURI) method of the Document object, you can open the XML file you just created. The URI format for this method uses a string expressed as a file:/// URI. It must be a full path to the file; relative paths will not work. Please make sure this path is correct for your machine. In the section "Creating an MXP installer for your JSFL command," you'll learn how to reference the folder to which you install the finished command. Save your files and click your command to see the Invisible Button dialog box open (see Figure 1).
Clicking either of the two buttons returns you to your JSFL script a result object, which you store in a variable cleverly named result. This object's dismiss property is the only predefined property and have a value of either "accept" or "cancel". The other properties will be defined by you in your XML file:
...
var result = flash_doc.xmlPanel("file:///C:/Documents and Settings/Username/Desktop/My Commands/Invisible Button.xml");
if (result.dismiss == "accept")
{
var symbolname = "invisible";
...
flash_doc.selectNone();
}
If you now run your command, you will notice that clicking OK adds the invisible button to the movie and clicking Cancel does nothing, as it should.
Take a look at the XML file. Open "Invisible Button.xml" in your text editor and start adding controls to the form. Each of the controls you add will, in turn, become properties of the xmlPanel's result object (see Figure 2).
Let's start with the layout controls. Use the <grid> container so that you can lay out your controls in a column/row format. As you can see, we need the form to be two columns wide: one for the field names and the other for the fields themselves. Since there are three form elements, and we have one per line, we'll need three rows.
The format is different than the <table> container you might expect from HTML. The <grid> container always contains exactly two children: <columns> and <rows>.
The <columns> container has two empty <column/> tags to let the engine know that each row contains two columns. When the engine parses a <row> tag, it puts the first element in the first column and the second element in the second column. For example, the first element in each row is a <label> control to display the name of the input control. The second element in each row is the input control. Finally, after the grid there's a <separator/> tag which draws a line between the form and its buttons.
This is how the complete "Invisible Button.xml" file should look:
<?xml version="1.0"?>
<dialog title="Invisible Button" buttons="accept,cancel">
<grid>
<columns>
<column/>
<column/>
</columns>
<rows>
<row>
<label value="URL:" />
<textbox id="url" tabindex="1" value="_root.clickTag" multiline="false" maxlength="100" />
</row>
<row>
<label value="URL Type:" />
<radiogroup id="type" tabindex="2">
<radio label="Variable" selected="true" />
<radio label="String" />
</radiogroup>
</row>
<row>
<label value="Target:" />
<textbox id="target" tabindex="3" value="_blank" multiline="false" maxlength="20" />
</row>
</rows>
</grid>
<separator/>
</dialog>
Each input control has an "id" attribute which becomes a property with the same name in the result object returned from xmlPanel(). This is how you will access the user-defined values in your JSFL file.
Save your updated XML file and run the command. You should now see the form, although none of the options actually has an affect on the button you are creating. You'll do that in the next step.
Now that you have the form laid out correctly, it's pretty simple to access its properties. Just access the property you need from the result object. To see the form work and the properties it returns, add this bit of code to our JSFL file:
...
var result = flash_doc.xmlPanel("file:///C:/Documents and Settings/Username/Desktop/My Commands/Invisible Button.xml");
// Add these three lines here to see all of the available
// properties of the result object
for (var prop in result) {
fl.trace("property " + prop + " = " + result[prop]);
}
if (result.dismiss == "accept")
{
...
Your output window should display something similar to the following:
property url = _root.clickTag
property type = Variable
property target = _blank
property dismiss = accept
Using these newly created properties, you can make the JSFL command respond to the options selected in the dialog box.
Here is the current state of the JSFL file. The code reflects the items I've discussed up to this point and will change as you continue this tutorial:
/**
* Invisible Button.jsfl (phase 2 of 3)
* JSFL Command that adds an invisible button to the library of an open
* FLA and places that button on to the current timeline at the height
* and width of the stage.
*
* Author: James O'Reilly - JOR
* www.jamesor.com
*
* This work is licensed under the Creative Commons attribution 2.5
* This comment block must remain intact.
*/
var flash_doc = fl.getDocumentDOM();
var flash_lib = flash_doc.library;
var flash_tl = flash_doc.getTimeline();
var flash_w = flash_doc.width;
var flash_h = flash_doc.height;
// Be sure to set this path to match your setup
var result = flash_doc.xmlPanel("file:///C:/Documents and Settings/Username/Desktop/My Commands/Invisible Button.xml");
if (result.dismiss == "accept")
{
// Make sure the result values are valid
// and use defaults if they are not
var mUrl = result.url;
if (mUrl == "")
mUrl = "_root.clickTag";
else
{
// If we are setting the URL to a string instead of
// using a variable name then we need to quote the url.
if (result.type == "String")
mUrl = "\"" + mUrl + "\"";
}
var mTarget = result.target;
// Additional Local Variables
var symbolname = "invisible";
// Check the library to see if the symbol already exists
// if it does, create a new one with the next highest
// number
while (flash_lib.itemExists(symbolname))
{
flash_lib.deleteItem(symbolname);
}
// Add the new symbol to the library and enter edit mode
flash_lib.addNewItem("button", symbolname);
flash_lib.editItem(symbolname);
// Draw the button
button_tl = flash_doc.getTimeline();
button_tl.insertKeyframe(3);
button_tl.currentFrame = 3;
flash_doc.addNewRectangle({left:0, top:0, right:flash_w, bottom:flash_h}, 0);
flash_doc.selectAll();
flash_doc.breakApart();
flash_doc.setFillColor("#000000");
flash_doc.setStroke("#00000000", 1, "solid");
flash_doc.selectNone();
// Exit edit mode and add the symbol to the stage
flash_doc.exitEditMode();
// Create a new layer for this symbol and rename it to button
var layerNum = flash_tl.addNewLayer();
flash_tl.setSelectedLayers(layerNum, true);
flash_tl.setLayerProperty("name", "button");
// Place the button on the stage and add the actionscript
flash_lib.addItemToDocument({x:flash_w/2, y:flash_h/2}, symbolname);
flash_doc.selection[0].actionScript = "on (release) {\n\tgetURL("+mUrl+", \""+mTarget+"\");\n}";
flash_doc.selectNone();
}
This section explains how to update the Invisible Button command so that you can use a relative installation path to locate your XML form. I also describe how to create an installer file (MXI) and package it (MXP) using Macromedia Extension Manager so that you can distribute your commands to the Flash community.
There are three things to accomplish in this tutorial:
Open the "Invisible Button.jsfl" file from the My Commands folder on your desktop.
The fl object contains a property called configURI. This property contains the file URI for the location of the current user's Flash configuration. Each machine user has a unique URI which, in Windows, is typically something similar to file:///C|/Documents and Settings/Username/Local Settings/Application Data/Macromedia/Flash 8/en/Configuration/. Inside this folder, you'll find the Commands folder, which contains files for any custom commands you might have installed already.
This is the folder you'll be targeting when you create your package, so change the xmlPath() to point to this folder when looking for the "Invisible Button.xml" file. To do that, simply change the beginning of your "Invisible Button.jsfl" script to read as follows:
var flash_doc = fl.getDocumentDOM();
var flash_lib = flash_doc.library;
var flash_tl = flash_doc.getTimeline();
var flash_w = flash_doc.width;
var flash_h = flash_doc.height;
fl.trace(fl.configURI);
var result = flash_doc.xmlPanel(fl.configURI + "/Commands/Invisible Button.xml");
We added the trace() function so that you could easily find out the exact folder you are targeting on your machine. You can remove this line after you find your folder. Create a new empty Flash document so that you can test this modified command (the Commands menu is available only when a FLA is open and in focus).
If you run the command, two things should happen. First, you should get an error (see Figure 3). Second, the Output window should display and print the fl.configURI for you to examine.
The error is expected because you do not yet have your XML file located in this new folder. The trace() finds the correct location to copy your XML file to. Read the path printed in the Output window and then open that folder however you wish. Then look for the Commands folder and open it. Copy only the "Invisible Button.xml" file into that Commands folder and run your command again. This time you should not get an error and the command should function as expected. You can now delete the trace() command at this point; you won't be needing it any longer.
Using your favorite text or XML editor, create a new document in your My Commands folder on your desktop called Invisible Button.mxi. Below is a basic template you can copy into this file:
<macromedia-extension
type="flash command"
version="1.0.0"
requires-restart="false"
name="Invisible Button">
<!-- children will go here -->
</macromedia-extension>
This is a starting point for the MXI that you'll be building on. Notice that the root node, <macromedia-extension>, has a number of settable attributes. The first attribute, type, defines the type of extension we are adding. For a complete list of extension types and further documentation on extensions, read The Extension Installation File Format (PDF, 420KB) . The second attribute, version, defines the version number for your extension. requires-restart indicates whether Flash needs to be restarted after the installation; it doesn't, for a command. You can look up other types of extensions for their requirements. The last attribute is name, the name of the command.
Inside the <macromedia-extension> node are a few child nodes that define the information displayed inside Extension Manager after users install the extension. The first node, <author>, defines the author's name as displayed inside Extension Manager:
<author name="James O'Reilly" />
The second node, <product>, defines the product that the extension will be installed to. This is because the MXI format is used across many Macromedia (now Adobe) products. Extension Manager needs to know which one to install to. Extension manager will install to the version indicated, or later:
<products>
<product name="Flash" version="8" primary="true" />
</products>
The next node defines the product's description:
<description>
<![CDATA[Creates an invisible button and places it on the document in a new layer named "button" and automatically assigned a getURL call. The button is created as a symbol called "invisible".]]>
</description>
<ui-access>
<![CDATA[Commands > Invisible Button]]>
</ui-access>
The next node defines any legal information you wish to display. It will be prompted for the user to accept before installating the extension:
<license-agreement>
<![CDATA[CDATA[James O'Reilly provides this extension 'as is' without any warranty of any kind as to the suitability of the extension for the licensees intended purpose. All risk as to the performance of the extension and it's contents are the sole responsibility of you, the licensee.]]>
</license-agreement>
Last comes the node that defines all the files that make up this extension. The source attribute defines the location of the file and the destination attribute defines the location of the installation. To install to the Commands folder, you can target that folder like this: $Flash\commands (case must match the actual folder name). Two files make up your installation, so you need to include both. Because this MXI file is in the same folder as the JSFL and XML files, the source doesn't need to list a path:
<files>
<file source="Invisible Button.jsfl" destination="$Flash\commands" />
<file source="Invisible Button.xml" destination="$Flash\commands" />
</files>
Note: If this isn't the case, you can use relative paths as needed.
After you create your MXI file, open Extension Manager. (If your Flash installation does not come with this program, you can download it for free.) From inside Extension Manager, select File > Package Extension and browse to the "Invisible Button.mxi" file you just created. When prompted, save the "Invisible Button.mxp" file to the same folder.
To install your new extension, select File > Install Extension from the Extension Manager menu. Browse to the newly created "Invisible Button.mxp" file. You will be prompted with a legal agreement. If you scroll down to the bottom, you'll see the additional legal information you added in your MXI file. Click OK to install. You're done! You'll see your new extension added to the list of currently installed Flash extensions in the Commands menu. Go ahead and create a new document and test your new command.
To distribute this extension, simply give out the MXP file. It contains all the necessary files others need to install the extension in Flash.
In this tutorial you've gone down a path which started at planning a project and ended at creating a polished installer for professional distribution. From a high-level view, you divided the project into three parts: command, interface, and installer. As you delved into each part, you further divided the tasks into smaller goals while testing your progress along the way.
Here are just a few ideas to help get you started writing your own JSFL commands:
A good book to read is Extending Macromedia Flash MX 2004 by Keith Peters and Todd Yard. It has comprehensive coverage of Flash extensions.
I also recommend checking out Peter Elst's article, Automating Tasks with JavaScript Flash. Peter covers JSFL basics and the new Filter object in Flash 8.
I always like to hear from people. Feel free to contact me with any questions or suggestions you might have. The best place to contact me is at my blog, www.jamesor.com. Happy coding!
| 04/23/2012 | Auto-Save and Auto-Recovery |
|---|---|
| 04/23/2012 | Open hyperlinks in new window/tab/pop-up ? |
| 04/21/2012 | PNG transparencies glitched |
| 04/01/2010 | Workaround for JSFL shape selection bug? |
| 02/13/2012 | Randomize an array |
|---|---|
| 02/11/2012 | How to create a Facebook fan page with Flash |
| 02/08/2012 | Digital Clock |
| 01/18/2012 | Recording webcam video & audio in a flv file on local drive |