10 October 2005
ActionScript and a basic understanding of the Fireworks DOM and working with Fireworks JavaScript.
Recommended reading:
Intermediate
When it comes to designing and optimizing web graphics, it doesn't get much better than Macromedia Fireworks—given its available tools and workflow. However, Fireworks is not necessarily limited to the tools that come with it in the default installation. You can modify the interface to include new, custom panels that unlock hidden functionality, introduce new functionality, or simply improve your workflow.
As a developer and designer, I find such extensions to the Fireworks interface invaluable in helping me quickly and easily obtain my graphics goal. In Part 1 of this tutorial, I will show you how you too can make your own custom panels for Fireworks using Macromedia Flash. Not only will I show you how they are made but I will also cover workflow tips for making panels and techniques to help you successfully debug them.
Custom panels in Fireworks are made with Flash. They consist simply of a Flash SWF file, which Fireworks plays using an internal Flash player embedded within a panel window that looks and operates like any other panel that's native to the application. The SWF provides the panel with its own interface and a means for interacting with Fireworks using ActionScript. If you've ever used the Align panel in Fireworks, you've seen a custom SWF panel at work (see Figure 1).
A custom panel window is capable of receiving commands from the SWF it's playing through ActionScript. One ActionScript command in particular instructs the window to relay information—or, more specifically, a JavaScript command—from the SWF to the main Fireworks application. This command is the MMExecute() command:
MMExecute( JavaScriptCommand:String ) : String;
MMExecute has one parameter, a string representing a Fireworks JavaScript command that is to be sent to Fireworks. When Fireworks receives this command, it runs it just as though it were on the Commands menu. When the command has completed, MMExecute also returns any result of that command back to Flash in the form of a string. This allows the SWF to react to any command sent by MMExecute if needed (see Figure 2).
In its simplest form, MMExecute is about all you need to create a simple panel and use it to interact with Fireworks. You can see how simple it is with the first example, the Create Ellipse panel.
This example represents your basic first panel example, similar to "Hello World" examples you might encounter when learning programming languages. Because Fireworks is a graphics editing program and not a new programming language, I will use an ellipse in place of "Hello World."
The Create Ellipse panel creates an ellipse in the current Fireworks document. The panel interface consists of a single button that, when clicked, creates the ellipse. As you might have guessed, MMExecute is used to instruct Fireworks to draw the ellipse when the button is clicked. The code for actually creating the ellipse can be written by hand if you are familiar enough with Fireworks JavaScript programming. But it's easier to extract it from the History panel after you first draw one yourself in Fireworks.
The History panel in Fireworks is much like the history panels in other applications that record your every action, step by step, within the program. In Fireworks, however—as well as other Studio applications such as Flash and Dreamweaver—the History panel records not only your steps but also the JavaScript command(s) used to generate those steps. You can therefore copy the JavaScript associated with any step(s) in your history to the Clipboard and reuse it elsewhere, such as in your custom commands or, as you will see in this example, custom panels.
Here is the procedure to get the code you need to create this panel:
Source Files/Templates/Create Ellipse.fla
It should consist of a Button component with the label "Create Ellipse."
on(click){
MMExecute("");
}
MMExecute call, paste the Fireworks JavaScript command you copied from the History panel in Fireworks. You should have something that looks like the following:on(click){
MMExecute("fw.getDocumentDOM().addNewOval({left:6, top:5, right:98, bottom:61});");
}
Figure 4 shows this code pasted into the Actions panel.
You now have a SWF containing a button that, when clicked, uses MMExecute to send a command to the Macromedia application running the SWF. Ideally this should be Fireworks but simply publishing the SWF does not make Fireworks recognize it as a panel.
Fireworks recognizes custom panels by scanning for SWF files located in the Configuration/Command Panels folder within the Fireworks installation folder whenever the application launches. Each SWF located there is interpreted as a panel and is listed along with those available in the Windows menu. The name given to the panel in Fireworks is simply the SWF's filename, not including the extension.
For your Create Ellipse SWF to be recognized as a panel, you must put it in the Command Panels folder:
(Windows) C:\Program Files\Macromedia\Fireworks 8\Configuration\Command Panels
(Mac OS) Macintosh Hard Drive:Applications:Macromedia:Fireworks 8:Configuration:Command Panels
You should see a new ellipse created in your Fireworks document. Its size and shape should match exactly the one you created previously in Fireworks in order to get the command code from the History panel.
The Create Ellipse panel was pretty straightforward. As you begin to make more complex panels, however, it will become important to develop a process that makes creating and testing your panels as painless as possible. This is important because you're not only dealing with two applications but also two programming languages that must interact to make your custom panels work. Developing a good workflow can help make the process run smoothly.
Although custom Fireworks panels use Flash SWF files to operate, the real functionality of the panel lies within the Fireworks JavaScript code that MMExecute sends to Fireworks from the SWF. When you decide to make a panel, it is often a good idea to start by creating Fireworks JavaScript scripts separately and testing them directly within Fireworks without a panel interface. That way, when you start working in Flash, you can at least know that you are working with functional Fireworks commands.
Dreamweaver 8 happens to be a great editor for creating and testing Fireworks JavaScript files. Although Dreamweaver 8 supports Fireworks JavaScript files, it does not inherently treat them like other JavaScript (.js) files. Included with this tutorial's source files is an extension which allows Dreamweaver to recognize JSF files as normal JavaScript files, providing code coloring and code hints:
Also included with the source files is another extension, a Command Prompt panel for Fireworks. This custom panel provides a very simple text editor for writing and executing Fireworks JavaScript from directly within Fireworks (see Figure 6).
Here's how you install the Command Prompt extension:
As you saw when creating the Create Ellipse panel, having access to the commands created by Fireworks through its History panel is a great time-saver. Use it whenever you can to create commands quickly for your scripts rather than typing them by hand. You may even be able to learn something new by looking at the code generated by the History panel.
Many Fireworks commands go beyond what you can retrieve from the History panel. When you create custom scripts, it is always a good idea to keep the Extending Fireworks documentation open and available for quick reference (available from the Help menu in Fireworks 8). Chances are good that you will need it—and need it often.
I suggest downloading the PDF version (ZIP, 1.2 MB) instead of the Help system. I find that searching within the PDF is more accurate than searching through Help. Plus you get to have every page open and easily accessible, unlike the segmented Help system. Be sure to keep up with the Fireworks 8 LiveDocs for any corrections and suggestions made by other Fireworks users.
When you work with Fireworks scripts, you may find that some scripts come in use over and over again. Keep a repository of these scripts so that you can easily reuse them if needed. If you are using Dreamweaver to write your scripts, make use of Dreamweaver's code snippets feature. For more information about the Snippets feature in Dreamweaver, see Working with Code Snippets in the Dreamweaver LiveDocs.
By default Flash publishes a SWF in the same folder that the FLA is located. For Fireworks to recognize a SWF as a panel, however, it needs to exist within the Command Panels folder located in the Fireworks installation folder. To avoid the hassle of manually putting it there yourself, you can instruct Flash to publish the SWF directly to that folder. There are two ways to do this:
Because Fireworks requires a panel SWF to reside in the Command Panels folder during startup, it's a good idea to publish your SWF immediately after creating your FLA, followed by a restart of Fireworks. That will make Fireworks recognize your SWF as a panel immediately. Then you can republish your SWF as needed to update that panel. Each time you publish, all you need to do is close and reopen the panel to reload the newest version of the SWF used by that panel.
Note: There are two ways to get rid of a panel in Fireworks: closing it and hiding it. When you reopen a panel, if it was hidden, it will not reload the SWF. The SWF is only reloaded if the panel is reopened after having been closed. To close a panel you must either right-click its title bar and select Close Panel Group or click the close button available to the panel when it's undocked. Hiding a panel happens when you reselect the panel name in the Windows menu when it is already opened. For undocked panels, hiding a panel in this manner makes it appear as though the panel were closed.
If you are dealing with a fairly complex panel—or one including many operations or options—it will be helpful to test each part of the panel separately. This lets you isolate problems as they occur and helps you deal with them more effectively. Attempting to debug a panel when you don't know what's causing the problem is much more aggravating.
Flash has quite a few tools to help developers debug panels specifically designed for it. Fireworks, on the other hand, has nothing of the sort. In fact, if you're lucky Fireworks will probably tell you simply that an error occurred by issuing an alert (see Figure 8). This can make Fireworks scripts quite difficult to debug.
There's a two-step process involved when Fireworks receives a Fireworks JavaScript command. First it evaluates the script and then it executes the script. A problem can occur at either step—and either step can produce an error alert. Such alerts, however, will not always be invoked if the script is run from within a Flash panel—a reason why testing outside of the panels themselves can be beneficial.
When Fireworks encounters a problem with evaluation, no script will be executed and an error will be immediately issued. This usually means there's been a syntax error such as mismatched brackets ( {} ) or a misuse of operators. There will be no indication of where the error occurs. It will be left up to you to find out where that is. More advanced external script editors like Dreamweaver 8 should help you find such errors, thanks to features such as color coding. The following example will create an error in Fireworks due to an error in script evaluation:
function copy () {
fw.getDocumentDOM().clipCopy();
copy();
The problem with the above script is that the copy function block was not closed with an ending bracket ( } ). As a result, Fireworks kills the command and issues an error before anything even gets to be executed.
Getting a script to evaluate correctly is half the battle. An error can still occur if there is a problem with execution. If that happens, Fireworks will issue an alert directly after reaching the problematic code. Problems in execution usually result from attempting to use a variable or function that does not exist or calling a native Fireworks method with incorrect arguments. The following example will create an error in Fireworks due to an error in script execution:
function copy () {
fw.getDocumentDOM().clipCopy();
}
copie();
Now the copy function is correctly defined here but when it is called at the bottom of the script, it's misspelled. Because the copie function doesn't actually exist, there is an error when Fireworks attempts to call it, which kills the script and issues the error alert.
Although the error message for both the previous examples is the same ("Could not run the script. An error occurred."), there is a slight advantage in dealing with errors resulting from execution. That advantage is the simple fact that code prior to the faulty code is correctly executed before Fireworks issues the error. This allows you to find out at least how far a script has gone before reaching bad code, or even if it manages to execute any code at all. The most common indicator is a simple JavaScript alert. Consider the following script:
alert("Defining copy function");
function copy () {
fw.getDocumentDOM().clipCopy();
}
alert("Calling copy function");
copie();
alert("Script Complete");
When you run this script in Fireworks, you get three alert messages, "Defining copy function," "Calling copy function," and the inevitable error, "Could not run the script. An error occurred." The fact that "Defining copy function" is alerted tells you immediately that the script was evaluated correctly and that there are no errors with the syntax. Seeing "Calling copy function" tells you that copy was defined correctly and that it is about to be called within the script. Given that the error message is seen instead of the "Script Complete" alert indicates that there was something wrong with the call to the copy function. Upon closer inspection, the spelling error can be identified and corrected.
The previously mentioned Command Prompt extension is a great way to help test scripts in Fireworks. Keep in mind, however, that because the Command Prompt extension is a custom SWF panel, it may fail to provide some Fireworks error alerts when it encounters a problem in the script. Including your own alerts when using the Command Prompt is nonetheless beneficial in determining whether a script is correctly interpreted or whether or not it is executed completely and successfully by Fireworks.
Fireworks actually provides a means to help you debug SWF panels as they send the script to the application. It provides two Fireworks JavaScript methods—fw.enableFlashDebugging() and fw.disableFlashDebugging()—that let you turn debugging mode on and off for commands sent to Fireworks. Don't get too excited, though. All that this debugging includes is an alert of the script being sent to Fireworks from a call to MMExecute. Chances are that you, being the panel developer, already know what that script is so using these methods may only help so much. Calling fw.enableFlashDebugging turns on this alert feature, while fw.disableFlashDebugging turns it off.
Here are some things to consider when debugging:
MMExecute uses a string for Fireworks JavaScript commands, so watch for quotes and character-escaping when sending a string from Flash to Fireworks. For example, this code will not work:MMExecute("alert("Hello");");
The open quote in the alert text actually closes the quote string used in MMExecute. Instead, you can use single quotes or escape nested double quotes with the backslash character:
MMExecute("alert(\"Hello\");");
If you find something that isn't working right, and you think it should, check the Fireworks LiveDocs. You'll find corrections to the Help documentation there and possibly solutions to whatever problem you may be encountering. Also feel free to ask questions in the Extending Fireworks Forum.
Now that you have a better understanding of debugging and improvements that can be made to your workflow, it's time to start with a new, slightly more complex example: the Mirror panel.
The Mirror Panel allows you to duplicate a selection in Fireworks by creating a mirrored copy of it (see Figure 9). It consists of four buttons which lets the user mirror a selected object above, to the right, below, or to the left of its original location.
Before opening the Mirror panel source file in Flash, you should first develop some working Fireworks JavaScript that will perform the mirror action, thereby avoiding any additional complications introduced by Flash. For this example, I presume that you have installed the Command Prompt extension and are using it to test the script. If you do not, you can write your script in Dreamweaver and run it by choosing Commands > Run Script from within Fireworks.
Instead of attempting to figure out how to code a mirror from scratch, let Fireworks do much of the hard work for you by manually mirroring an object yourself and then copying the code found in the History panel:
A mirror of the original object has now been created. To see how it happened in terms of Fireworks JavaScript, take the relevant code from the History panel (see Figure 10).
You now have the steps required to mirror your test object. To see if they work, delete your test object, reselect the original, and run the steps in the Command Prompt using the Execute button. If you copied the steps correctly, your mirrored object should return just as it was before you deleted it.
There still remains a small problem with the script as it exists in this form. The problem is that the position of the copy is currently hard-coded—based on where you dragged the copy when using your test object. Ideally the mirror script should work for any object of any size, and place the mirrored copy flush against the edge of the original. To make this happen, the script needs to be edited a little to take in account a dynamic offset based on the selection's width.
Assuming that one object is selected, you can get the selection's width using fw.selection[0].width where fw.selection[0] represents the first object in the current document's selection. Moving the copy by a distance of the selection's width will place it right next to the original.
With that in mind, edit the script so it looks like the following:
fw.getDocumentDOM().moveSelectionBy({x:fw.selection[0].width, y:0}, true, false);
fw.getDocumentDOM().reflectSelection(true, false, "autoTrimImages transformAttributes");
That takes care of mirroring an object to the right, but this panel should also mirror it to the left, top, and bottom as well. Referencing the script above, you can make versions for each direction.
Left:
fw.getDocumentDOM().moveSelectionBy({x:-fw.selection[0].width, y:0}, true, false);
fw.getDocumentDOM().reflectSelection(true, false, "autoTrimImages transformAttributes");
Top:
fw.getDocumentDOM().moveSelectionBy({x:0, y:-fw.selection[0].height}, true, false);
fw.getDocumentDOM().reflectSelection(false, true, "autoTrimImages transformAttributes");
Bottom:
fw.getDocumentDOM().moveSelectionBy({x:0, y:fw.selection[0].height}, true, false);
fw.getDocumentDOM().reflectSelection(false, true, "autoTrimImages transformAttributes");
After making sure the scripts work, save them and move on to Flash:
You have now established the Mirror panel in the Fireworks interface. You should be able to open it in Fireworks using Window > Mirror. It won't do anything at this point, but you at least have your foot in the door. To test your updates to the panel, simply close and reopen the panel window. The SWF will be updated with the last SWF you published.
With all the groundwork set and the panel interface complete, you can start adding ActionScript. Like the Create Ellipse example earlier, the ActionScript for the Mirror panel is pretty basic. You're only dealing with button actions and using MMExecute to send Fireworks JavaScript commands to Fireworks within them.
Given that there are four buttons, it would be best if the ActionScript was added to the Timeline (as opposed to directly on the buttons—something I did for the Create Ellipse panel for the sake of simplicity and convenience). In each button's onRelease event handler, you can use MMExecute to relay the Fireworks JavaScript commands to Fireworks. With the current scripts, however, you're now dealing with two lines of code instead of the one seen in Create Ellipse. That's no problem. Each line can go in its own MMExecute function.
onRelease event handlers to each button: Notice that a single quote ( ' ) was used to define the Fireworks JavaScript string sent into MMExecute. This is to prevent conflicts with the double quotes ( " ) used in reflectSelection for "autoTrimImages transformAttributes". You could have also escaped the double quotes within the script using \". Using single quotes is often easier, however.right_btn.onRelease = function(){
MMExecute('fw.getDocumentDOM().moveSelectionBy({x:fw.selection[0].width, y:0}, true, false);');
MMExecute('fw.getDocumentDOM().reflectSelection(true, false, "autoTrimImages transformAttributes");');
}
left_btn.onRelease = function(){
MMExecute('fw.getDocumentDOM().moveSelectionBy({x:-fw.selection[0].width, y:0}, true, false);');
MMExecute('fw.getDocumentDOM().reflectSelection(true, false, "autoTrimImages transformAttributes");');
}
top_btn.onRelease = function(){
MMExecute('fw.getDocumentDOM().moveSelectionBy({x:0, y:-fw.selection[0].height}, true, false);');
MMExecute('fw.getDocumentDOM().reflectSelection(false, true, "autoTrimImages transformAttributes");');
}
bottom_btn.onRelease = function(){
MMExecute('fw.getDocumentDOM().moveSelectionBy({x:0, y:fw.selection[0].height}, true, false);');
MMExecute('fw.getDocumentDOM().reflectSelection(false, true, "autoTrimImages transformAttributes");');
}
With any luck, you should have a fully functional Mirror panel in Fireworks (see Figure 12).
Though functionally the panel works fine, you may notice a small flaw if you pay close attention to it. Watch the History panel as you use the Mirror panel; you will see that each time an object is mirrored, two history steps are created instead of one. This is because MMExecute is used twice each time a button in the Mirror panel is clicked. To keep History steps as condensed as possible, it would be best to reduce this to one.
This new script combines each set of MMExecute commands into one. Replace it in Flash, republish, and retest:
right_btn.onRelease = function(){
var command = 'fw.getDocumentDOM().moveSelectionBy({x:fw.selection[0].width, y:0}, true, false);'
+ 'fw.getDocumentDOM().reflectSelection(true, false, "autoTrimImages transformAttributes");';
MMExecute(command);
}
left_btn.onRelease = function(){
var command = 'fw.getDocumentDOM().moveSelectionBy({x:-fw.selection[0].width, y:0}, true, false);'
+ 'fw.getDocumentDOM().reflectSelection(true, false, "autoTrimImages transformAttributes");';
MMExecute(command);
}
top_btn.onRelease = function(){
var command = 'fw.getDocumentDOM().moveSelectionBy({x:0, y:-fw.selection[0].height}, true, false);'
+ 'fw.getDocumentDOM().reflectSelection(false, true, "autoTrimImages transformAttributes");';
MMExecute(command);
}
bottom_btn.onRelease = function(){
var command = 'fw.getDocumentDOM().moveSelectionBy({x:0, y:fw.selection[0].height}, true, false);'
+ 'fw.getDocumentDOM().reflectSelection(false, true, "autoTrimImages transformAttributes");';
MMExecute(command);
}
Now using the Mirror panel should result in only one History step.
With everything complete and working properly, there is one more concern that you may want to address: code reuse. Mirroring an object in Fireworks is a potentially useful action. If you should ever want to perform this action again, it would be helpful to have an easily reusable snippet of code that would let you do so. At this point, the code is pretty divided, having different implementations between four different buttons. Using a single function to represent a mirror in any direction would be useful and reusable. Such a function may look like the following:
function mirror(horiz, vert){
var dom = fw.getDocumentDOM();
dom.moveSelectionBy({x:horiz*fw.selection[0].width, y:vert*fw.selection[0].height}, true, false);
dom.reflectSelection(Boolean(horiz), Boolean(vert), "autoTrimImages transformAttributes");
}
Where horiz and vert represent directions of mirroring. Passing the value (–1, 0) to the mirror function mirrors to the left. Using (0, 1) mirrors to the bottom. Implemented in the Mirror panel, it looks like this:
var mirror = 'function mirror(horiz, vert){'
+ ' var dom = fw.getDocumentDOM();'
+ ' dom.moveSelectionBy({x:horiz*fw.selection[0].width, y:vert*fw.selection[0].height}, true, false);'
+ ' dom.reflectSelection(Boolean(horiz), Boolean(vert), "autoTrimImages transformAttributes");'
+ '}';
right_btn.onRelease = function(){
MMExecute(mirror);
MMExecute('mirror(1, 0);');
}
left_btn.onRelease = function(){
MMExecute(mirror);
MMExecute('mirror(-1, 0);');
}
top_btn.onRelease = function(){
MMExecute(mirror);
MMExecute('mirror(0, -1);');
}
bottom_btn.onRelease = function(){
MMExecute(mirror);
MMExecute('mirror(0, 1);');
}
You may notice that this script is back to using MMExecute twice again. It won't, however, create two steps in the History panel as the original version using two MMExecute commands for each mirror did. This is because Fireworks registers History steps from JavaScript commands only if there was some reaction made as a result of the executed command. Defining a function causes no such reaction so no History step is recorded. Actually, calling the function does cause the history to be recorded, so only the second call to MMExecute will result in a history step.
The two panels I discussed in this tutorial provided very basic examples of custom panels in Fireworks. A lot of focus was directed towards workflow and debugging, helping you prepare for when you decide to develop more complicated panels.
In Creating Fireworks Panels – Part 2: Advanced Custom Panel Development, I cover a more complex example dealing with the advanced capabilities of custom panels in Fireworks. You will see how you can use the return result of MMExecute to dictate actions in Flash as well as learn how to create some more advanced interfaces for panels using some new features available in Studio 8.
| 09/07/2011 | How do I use FXG XML markup in Shape subclasses? |
|---|---|
| 10/15/2010 | Flex4 Dotted Line |
| 06/25/2010 | ComboBox that uses a NativeMenu (Air API) |
| 05/21/2010 | Localizing a Creative Suite 5 extension |