14 November 2005
ActionScript and a basic understanding of the Fireworks DOM and working with Fireworks JavaScript.
Advanced
Custom panels in Fireworks 8 allow you to unlock hidden functionality in the product, introduce new functionality to the product, or simply improve your workflow. In Part 1 of this series, I showed you how to make your own custom panels for Fireworks using Macromedia Flash.
Creating Fireworks Panels – Part 1: An Introduction to Custom Panels ›
In the custom panel examples in Part 1, I paid little attention to their interfaces—which consisted mainly of simple buttons within a relatively small, static work area. Although such panels are fully functional, more attention could be given to their interface design and usability.
I also covered only basic panel functionality. Both the Create Ellipse and Mirror panels did nothing more than call basic Fireworks JavaScript commands, which were initiated by interacting with the interface. Custom Fireworks panels, however, are capable of much more than that.
Part 2 covers more advanced panel development, including interface design, working with the exchange of information between a custom panel and Fireworks, and taking advantage of additional features, such as Fireworks events.
I address each of these topics by working through a single panel example: the Annotations panel.
The Annotations panel is a custom panel in Fireworks that allows you to leave notes or annotations in a Fireworks document, either for the document itself or for individual elements within that document (see Figure 1).
The current user's name appears at the top of the panel. This identifies who edits or creates the annotations. (Basically, this means you.) Next to the user name is a button that allows you to change the annotation. Below that is the main annotation text area. This is where you create, edit, and view annotations for your document and those elements within it. The Save button saves the current annotation to the current selection.
Each element in a Fireworks document—path, bitmap, or even a slice—can have its own annotations, and they appear in this panel just by selecting the element. If no elements are selected, the annotations displayed will represent those for the entire Fireworks document. The status bar indicates who last edited the current annotation and when.
The Annotations panel template FLA (see Figure 2) is provided in the source files you downloaded at the beginning of this tutorial.
You can find it here in the sample download:
Source Files/Templates/Annotations.fla
Open it in Flash, publish the SWF to the Fireworks Command Panels folder, and then restart Fireworks to make sure Fireworks recognizes Annotations as a panel.
Note: The Annotations panel I discuss here is not compatible with my original Annotations panel. If you have been using the original panel, use the Convert Annotations command provided with the source files to convert older annotations in a Fireworks document into the format that's compatible with this newer panel.
When you first open a panel, Fireworks displays it at the size specified by the SWF document that makes up the interface. Users can resize custom panels all they want in Fireworks, but not smaller than the original size of the SWF. By default, when a panel is scaled, Flash Player scales the SWF to match the dimensions of the panel window. As a result, your panel interface may grow unattractively beyond its designed size. At times this may be desirable, but often it is not.
Luckily, Flash allows you to prevent a SWF scaling with the player area by using ActionScript. You can achieve this by setting the scaleMode property of the Stage object to "noScale", as follows:
Stage.scaleMode = "noScale";
A panel SWF with a scale mode of "noScale" will retain its original size despite the size of the panel window, centering itself in whatever area the panel provides.
However, it is not typical that a panel will center itself within the area of the panel. As you may notice with the Align or Auto Shape Properties custom panels, they are positioned instead at the top left of the panel area. Again, using the Stage object in ActionScript, you can allow for this by setting the align property to "TL" (for top left):
Stage.align = "TL";
Something else you may notice about most panels is that they are the same width—around 200 pixels or so. This allows them to fit alongside other docked panels at the side of the Fireworks workspace. If any panel were too wide, the other panels would have to stretch to match its width. When creating your own panels, keep your panel's width at around 200 pixels.
The Annotations panel was designed to be as small as possible, while still being functional. It uses scaleMode and align to make sure it remains fixed to the top left of Flash Player and does not scale. Add the scaleMode and align property definitions to the ActionScript layer in the Annotations FLA and publish a SWF to test it in Fireworks (see Figure 3).
Top-left alignment and no scaling may become your preference for panel interfaces, but there are additional options available to you. I like to think that a well designed panel makes full use of the available Flash Player space provided by the panel window. For example, the contents of the Layers panel resizes horizontally with the size of the panel window and can even scale down vertically to be smaller than its contents. Although the Layers panel is not itself a custom SWF panel, its behavior can mimic your custom panels.
Scaling panels relies on the ActionScript Stage.onResize event to detect when the available size of the player has changed. When that event is called, the SWF moves and scales the elements in the interface to match the new size:
Stage.addListener(this);
function onResize(){
// resize your movie
}
The more elements within your Flash interface, the more complex the onResize event handler because it needs to take the scaling of most, if not all, of those elements into consideration.
There are seven elements in the Annotations panel that are each going to be sized with the interface (see Figure 4):
user_label)edituser_button)annotations_ta)save_button)editeduser_label)editedtime_label)face_mc)
Each element moves or scales based on either the size of Flash Player or another element within the interface. For example, the annotations text area scales horizontally to the right based on the player's width but scales vertically based on the location of the Save button. The Save button moves vertically based on the position of the last user's edited label, which is based on the position of the date-last-edited label, which is based on the position of the bottom of Flash Player.
You could base the annotations text area off of the height of Flash Player. However, if an element between the annotations text area and the player's edge changes in size, the annotations text area would not be able to compensate for that, and there might be an overlap. Because some elements are based on the size of others, you need to make sure to adjust elements with dependencies last. That means positioning and sizing the Save button before the annotations text area.
Because of the not-so-robust nature of the UI components in Flash, an additional check is needed within the onResize event. Despite the fact that the user cannot manually scale a panel window to be smaller than the dimensions of the SWF it's playing, that does not mean that the panel won't ever be smaller than those dimensions. In fact, there are certain conditions in Fireworks where panels are resized to unexpected sizes internally when collapsed. Although this is not visible to the user, it can wreak havoc on your Flash components because many UI components will break if they are inappropriately sized. To prevent this from happening, include a condition in your onResize event handler that makes sure the scaling size does not fall below that of the movie at its smallest size. For the Annotations panel, the sizes for the height and width are 150 and 160 pixels, respectively.
Also, when a SWF is loaded into a Fireworks panel, the onResize event is not immediately handled. You may want to force a call to onResize when the movie starts so that it can display the panel interface correctly when opened:
Stage.addListener(this);
function onResize(){
var PADDING = 5;
var width = Math.max(150, Stage.width);
var height = Math.max(160, Stage.height);
face_mc._width = width;
face_mc._height = height;
edituser_button.move(width - edituser_button.width - PADDING, PADDING);
user_label.move(PADDING, PADDING);
user_label.setSize(edituser_button.x - PADDING*2, null);
editedtime_label.setSize(width - PADDING*2, null);
editedtime_label.move(PADDING, height - editedtime_label.height);
editeduser_label.setSize(width - PADDING*2, null);
editeduser_label.move(PADDING, height - editedtime_label.height - editeduser_label.height);
save_button.move(width - save_button.width - PADDING, editeduser_label.y - save_button.height - PADDING);
annotations_ta.move(PADDING, user_label.y + user_label.height + PADDING);
annotations_ta.setSize(width - PADDING*2, save_button.y - (edituser_button.y + edituser_button.height) - PADDING*2);
}
onResize();
Notice that a PADDING variable is used to help control padding within the interface. Using a variable like this makes it easier to perform adjustments in spacing elements. Add this code to the ActionScript layer in the Annotations FLA, publish the SWF, and then test the panel in Fireworks. The interface now stretches to meet the size of the panel (see Figure 5).
Fireworks 8 introduces a new core object for Fireworks JavaScript called the System object. This object provides information about the system that the current version of Fireworks is running on. More specifically, it provides the name of the operating system and system colors if the operating system is Microsoft Windows. You can use this object to help you develop panels whose colors match the color scheme of the user's computer (but only if it's Windows).
Table 1 lists the 10 System object properties, mostly related to colors associated with part of a Windows color scheme (see Figure 6).
| Property | Description | Windows Color |
|---|---|---|
osName |
Returns the name of the current operating system (e.g., “Windows XP” or “Macintosh 10.3.9”) | N/A |
controlDarkShadowColor |
Returns the system color used for control dark shadows | 3D Objects (auto) |
controlFaceColor |
Returns the system color used for control and panel faces | 3D Objects |
controlHighlightColor |
Returns the system color used for control highlights | 3D Objects (auto) |
controlShadowColor |
Returns the system color used for control shadows | 3D Objects (auto) |
highlightItemColor |
Returns the system color used for highlighting selections | Selected Items |
highlightTextColor |
Returns the system color used for highlighting selected text | Selected Items: Font |
menuColor |
Returns the system color used for menu backgrounds | Menu |
menuTextColor |
Returns the system color used for text in menus | Menu: Font |
textColor |
Returns the system color used for text | Window/Message Box: Font |
Because these colors are specific to the Windows operating system, Mac OS users should stick to the default colors of your panel. A good choice is white, as used by the Annotations panel.
For Windows users, the Annotations panel uses the System object to determine the panel's face color using menuColor. However, menuColor is a property in Fireworks JavaScript, not ActionScript. To get the value of that property into Flash, use MMExecute as described in the next section.
Part 1 of this series used the ActionScript command MMExecute to run Fireworks JavaScript from a Flash panel in Fireworks. Passing a string into MMExecute executes it as a Fireworks JavaScript command in Fireworks. MMExecute doesn't just execute a command string; it also returns the result of that command back to Flash.
MMExecute uses the value of the last code line in a JavaScript command to be sent back to Flash. This value is returned in Flash as a string, no matter what type it was in the command as JavaScript. This is important to remember. For example, if a command returns either true or false, MMExecute would return either the strings “true” or “false” when running the command, not the values true or false. If there is no value to be returned, the value given to Flash is an empty string.
For the Annotations panel, MMExecute simply runs the string “System.menuColor;” in order to get the value of menuColor sent to Flash as a string:
var faceColor = MMExecute('System.menuColor;');
The faceColor variable in Flash is then assigned a string in the form of “#RRGGBB”, depending on the user's system color for the menu. Assuming the user is running Windows, this can be used to give the face movie clip—properly formatted to a usable Flash color, of course. This is defined in a function called setFaceColor which is run when the movie starts:
function setFaceColor(){
if (MMExecute('System.osName;') == "Windows XP"){
var faceColor = MMExecute('System.menuColor;');
faceColor = parseInt(faceColor.substr(1), 16);
var faceColorTransform = face_mc.transform.colorTransform;
faceColorTransform.rgb = faceColor;
face_mc.transform.colorTransform = faceColorTransform;
}
}
setFaceColor();
Add this code to the Annotations FLA and then publish it for Fireworks to see the results (see Figure 7).
The purpose of the Annotations panel is to save text for a Fireworks document, whether it relates to elements within the document or the document itself. For this information to be retained within the document, the text has to be stored in such a way that will let it remain with that document even after it's closed and reopened later (see Figure 8). Doing this requires making use of Fireworks JavaScript objects like customData and pngText.
Each element in a Fireworks document has a persistent data structure in JavaScript called customData. In this object you can store variables specific to that element that will retain their values even if the file is closed and reopened at a later time. The customData object of the current selection would be accessed using the following:
fw.selection[0].customData;
The document itself doesn't have a customData object. The closest thing the document has is something called pngText, an object that stores the document creation time and the software used to create the document. The pngText object, however, is also capable of storing additional data. In fact, you can assign any number of additional properties to pngText (creation time and the software used are already stored, respectively, as CreationTime and Software). The only downside is that unlike customData, which can store just about anything, the values of pngText properties are limited to strings. Luckily, all that the Annotations panel requires is a string, so pngText works as a suitable alternative to customData for saving document-based annotations. You can access the pngText object for the current document using the following:
fw.getDocumentDOM().pngText;
For the document and any element in the document, the Annotations panel will need to store three kinds of textual information: annotation text provided by the user, user's name as indicated in the panel (also provided by the user), and time of the last edit. Each is stored as strings in either the pngText or customData objects. This information is accessed and read when the panel updates to display annotation information for the current selection and is saved back to that selection when the user clicks the Save button.
To make sure this data doesn't conflict with other panels or commands that use these objects, it's prudent to store information in new variables in pngText or customData that are named after your panel. In the case of the Annotations panel, an Annotations property is assigned to selected pngText or customData objects during the save:
fw.getDocumentDOM().pngText.Annotations = "some string value";
fw.selection[0].customData.Annotations = "some string value";
The saved information, however, consists of three values (annotation text, user name, and edit time), not just one. Two additional variables could be added to the customData and pngText objects but this could become confusing and cumbersome, especially when you make panels that use more than just three values. Also, don't forget that these values need to make their way into Flash. That could require a call to MMExecute for each variable used. As an alternative, you can use a single string to contain all three variable values, and then store it in the form of a URL-encoded variable string.
A URL-encoded variable string lets you store values for multiple variables in one string. If you are unfamiliar with them, chances are you've seen them in URLs on the Internet. They have the following form:
variable1=value1&variable2=value2&variablen=valuen
Equal signs (=) separate a variable name from its value and each variable is separated by an ampersand (&). When it's decoded, you get separate variables each with their separate values.
Flash can handle URL-encoded variable strings with the LoadVars class, which is usually used to obtain information from external URLs but can also be used to encode and decode URL-encoded variable strings. Because the Annotations panel handles all saved and retrieved content within Flash, this makes URL-encoded variable strings a great way to store the annotation text, user name, and last edited values all in one string with minimal hassle.
Now, you can store annotation information like the following:
fw.getDocumentDOM().pngText.Annotations = "text=Hello%20World&user=Trevor&time=9%3A06%3A11am%20-%20Tue%20Oct%2025%202005";
This definition contains three variables: text, user, and time. Each contains a value that is URL encoded (or “escaped”) within the string.
The getAnnotation function retrieves an annotation's variable string. When obtaining the value, however, you will need to figure out if you're dealing with pngText or a customData object, and check to make sure the Annotations property actually exists. This is all handled in a JavaScript command that is executed ultimately with MMExecute in Flash:
function getAnnotation(){
var data = {};
if (fw.selection.length) data = fw.selection[0].customData;
else if (fw.getDocumentDOM()) data = fw.getDocumentDOM().pngText;
return (data.Annotations != undefined) ? data.Annotations : "";
}
In Flash the recovered string is sent to a LoadVars instance that can parse the string into separate variables. You can call this LoadVars instance FWVariables. A function in Flash calls the JavaScript command and sends the results to FWVariables. Then, the interface is updated with the new content:
var FWVariables = new LoadVars();
function getAnnotation(){
var cmd = 'function getAnnotation(){'
+' var data = {};'
+' if (fw.selection.length) data = fw.selection[0].customData;'
+' else if (fw.getDocumentDOM()) data = fw.getDocumentDOM().pngText;'
+' return (data.Annotations != undefined) ? data.Annotations : "";'
+'}'
+'getAnnotation();';
var variable_str = MMExecute(cmd);
FWVariables = new LoadVars();
FWVariables.decode( variable_str );
update();
}
The Update function simply takes the variables in FWVariables and applies them to the affected places within the interface—the annotation's text area and the two labels in the status bar indicating when the annotation was last edited (and by whom). If no variables for those values exist, you can set the text to be an empty string or use default text in its place:
function update(){
if (FWVariables.text){
annotations_ta.text = FWVariables.text;
}else{
annotations_ta.text = "";
}
if (FWVariables.user){
editeduser_label.text = "<b>Last edited by:</b> " + FWVariables.user;
}else{
editeduser_label.text = "---";
}
if (FWVariables.time){
editedtime_label.text = FWVariables.time;
}else{
editedtime_label.text = "---";
}
}
When you save an annotation from the panel, these variables have to be updated and saved back to the object to which they relate—either a customData object or pngText depending on the current selection. As with what was done with getAnnotation, a setAnnotation in JavaScript is used to do this:
function setAnnotation(str){
if (fw.selection.length){
fw.selection[0].customData.Annotations = str;
}else{
fw.getDocumentDOM().pngText.Annotations = str;
}
}
When you save an annotation, the annotation's text value is taken directly from its respective text in the panel. The user value is taken from the name set by the user. This is retrieved by the getUser() function, which I will cover later. The remaining variable, time, has the only dynamically generated value. The Date() function in Flash is used for this to provide a default output. If you want a different format, you could use a separate function for a custom date output in its place.
However, each of these values, when added to the variable string, needs to be properly encoded. This allows you not only to retrieve the values again later using a LoadVars object, but it also helps prevent complications when quote characters mess with the MMExecute command string by sending values from Flash ActionScript to Fireworks JavaScript. You could do this manually using the escape() function in Flash but it is much easier just to use the LoadVars instance FWVariables and have it URL-encode the variables for you using its toString() method.
After you save the annotation, the interface is updated with update to reflect the new values. The addEventListener method is used to associate the setAnnotation function with the clicking of the Save button:
function setAnnotation(){
var cmd = 'function setAnnotation(str){'
+' if (fw.selection.length){'
+' fw.selection[0].customData.Annotations = str;'
+' }else{'
+' fw.getDocumentDOM().pngText.Annotations = str;'
+' }'
+'}';
FWVariables.text = annotations_ta.text;
FWVariables.user = getUser();
FWVariables.time = Date();
MMExecute(cmd);
MMExecute('setAnnotation("' + FWVariables.toString() + '");');
update();
}
save_button.addEventListener("click", setAnnotation);
Add the update, getAnnotation (ActionScript version), and setAnnotation (ActionScript version) functions to the Annotations FLA. You can publish and test it now in Fireworks but you won't see much of a result. This is because getAnnotation is not yet being called, so there is no way to see the annotations for any of your selected objects (or the document). At this point, about the best you can do is use the Command Prompt panel to check whether setAnnotation is, in fact, working (see Figure 9).
The setAnnotation function has a button that calls it. It's a little different with getAnnotation because it needs to be called whenever the selection changes in Fireworks. For that, you need to take advantage of Fireworks events.
Panels in Fireworks are capable of receiving events from the Fireworks application indicating when certain things happen within the Fireworks application. These operate much in the same way as Stage.onResize, except that these events are specific to SWFs being used as Fireworks panels. Table 2 lists all events that are available to custom SWF panels in Fireworks 8.
| Property | Description |
|---|---|
onFwStartMovie |
Sent to the SWF file right after Fireworks starts (or restarts) the SWF file |
onFwStopMovie |
Sent to the SWF file right before Fireworks stops the file (and possibly unloads it) |
onFwUnitsChange |
Sent when the user changes the type of units (inches, pixels, centimeters) in the Info panel |
onFwPICollapseOrExpand |
Sent when the user switches the Property inspector between two rows high and four rows high |
onFwDocumentNameChange |
Sent when the name of the current document changes (for example, when the user performs a save) |
onFwCurrentFrameChange |
Sent when the user selects a different frame |
onFwCurrentLayerChange |
Sent when the user selects a different layer |
onFwHistoryChange |
Sent when the user creates an non-scriptable history step |
onFwIdle0 |
Sent when Fireworks is in the first of a sequence of idle states (because Fireworks may often go through a sequence of idle states, triggering functions by this event may impair application performance) |
onFwIdle1 |
Sent when Fireworks is in the second of a sequence of idle states (because Fireworks may often go through a sequence of idle states, triggering functions by this event may impair application performance) |
onFWIdle2 |
Sent when Fireworks is in the third of a sequence of idle states (because Fireworks may often go through a sequence of idle states, triggering functions by this event may impair application performance) |
onFwApplicationDeactivate |
Sent when the Fireworks application loses focus |
onFwApplicationActivate |
Sent when the Fireworks application gains focus |
onFwSymbolLibraryChange |
Sent when the symbol library changes in some way |
onFwURLListChange |
Sent when a new URL is added to the document |
onFwFavoritesChange |
Sent when the favorite URLs list is modified |
onFwPreferencesChange |
Sent when the preferences are changed (called when the user clicks OK in the Preferences dialog box) |
onFwDocumentOpen |
Sent when the document is opened |
onFwDocumentClosed |
Sent when the document is closed |
onFwDocumentSave |
Sent when a save action is performed in the document |
onFwDocumentSizeChange |
Sent when the document is resized |
onFwActiveViewChange |
Sent when the active view changes (when the user changes focus in 2-Up or 4-Up view) |
onFwPixelSelectionChange |
Sent when the pixel selection changes |
onFwActiveSelectionChange |
Sent when the selection changes in a document |
onFwActiveDocumentChange |
Sent when the user creates a new document, closes a document, opens a document, or switches between open documents |
onFwActiveToolParamsChange |
Sent when the user changes the tool stroke or fill attributes |
onFwActiveToolChange |
Sent when the user changes tools (a fwActiveToolForSWFs property is added to _root indicating the current tool when this event is used) |
onFwZoomChange |
Sent when the zoom setting for the current document changes |
onFwObjectSettingChange |
Sent when a stroke or fill setting is changed for the selected object |
Note: Some bugs still exist with a few of these events. The onFwHistoryChange, onFwObjectSettingChange, and onFwPixelSelectionChange events, for example, are unreliable. Also be aware that certain events like onFwURLListChange and onFwFavoritesChange often fire twice in a row when they respond to the event in which they are associated.
A SWF panel does not automatically receive all of these events; it needs to register for the events it intends to use. This mostly automatic process simply requires you to create an event handler for each event desired in first frame of the main Timeline, defined either in the _root or the _global objects. Fireworks scans the SWF when it is first loaded looking at all the functions in _root and _global. When it finds functions it recognizes as being event handlers, it registers the panel to receive those events. For example, if you wanted a custom panel to play a sound whenever you close a document, you might define the following in the main Timeline of your custom panel movie:
function onFwDocumentClose(){
var vo = new Sound(this);
vo.attachSound("IHopeYouSaved.wav");
vo.start();
}
The Annotations panel uses events to recognize when a selection has changed so it can display annotations that relate to the current selection (or the main document if there is no selection). For this, the onFwActiveSelectionChange event is used because it indicates when the selection changes in Fireworks. When it's called, the Annotations panel simply needs to retrieve the annotation based on the current selection.
There is a catch, however. Certain operations in Fireworks—namely editing text and using the transform tools—continuously update the selection, invoking frequent onFwActiveSelectionChange events. The problem with this is that executing any kind of JavaScript command during these particular operations prevents those operations from working correctly.
Fireworks requires a JavaScript command's full attention when it is being executed. This means that Fireworks may drop whatever it's currently doing and change its focus to the command you're executing. This fact becomes important in two cases: when editing a text object and using the transform tool.
Calling a command while editing a text object can result in prematurely deselecting the text. Calling a command while using a transform tool (scale, skew, or distort) will lose the transformation selection, preventing additional transformations from occurring. These are obviously undesirable effects. Given that editing text and transforming a selection invokes the onFwActiveSelectionChange event, steps are required to prevent commands from being executed during these circumstances.
So what can you do? How about checking to see which tool is being used and then preventing commands from being called whenever it's the text tool or a transform tool? Although this is a great idea, the conventional means for obtaining the active tool in Fireworks is by none other than a command:
var activeTool = MMExecute("fw.activeTool");
And yet the whole point is not to run any commands at all.
Thankfully there is a workaround for this issue. When Fireworks recognizes an onFwActiveToolChange event handler defined for a SWF panel, it sets a variable in the main Timeline called onFwActiveToolChange, which Fireworks updates automatically as tools in Fireworks change:
var onFwActiveToolChange;
onFwActiveToolChange = function(){}
No JavaScript code is required in onFwActiveToolChange to make this work because it's all handled internally. The automatic fwActiveToolForSWFs variable provides the current tool's name as a string for you. Those relating to the text and transform tools in the English version of Fireworks include: "Text", "Scale", "Skew", and "Distort".
Bear in mind that these do change depending on the language of your Fireworks installation. You may need to take that into consideration when developing your panel. You can determine the current language using Files.getLanguageDirectory().
Now, to assure that the Annotations panel updates its selection without interrupting element transformations or text editing (assuming an English installation of Fireworks), you can use the following:
function onFwActiveSelectionChange(){
if (fwActiveToolForSWFs != "Text"
&& fwActiveToolForSWFs != "Scale"
&& fwActiveToolForSWFs != "Skew"
&& fwActiveToolForSWFs != "Distort"){
getAnnotation();
}
}
Some other events might be useful for checking for a change in selection. One would be the onFwActiveDocumentChange event. This event is fired when you switch the view in Fireworks to another document.
Instead of repeating the code used in onFwActiveSelectionChange, however, you can just call onFwActiveSelectionChange within onFwActiveDocumentChange:
var fwActiveToolForSWFs;
function onFwActiveToolChange(){}
function onFwActiveDocumentChange(){
onFwActiveSelectionChange();
}
Another event, onFwStartMovie, can be added to the list but can also include other startup functions such as onResize and setFaceColor:
function onFwStartMovie(){
onFwActiveSelectionChange();
setFaceColor();
onResize();
}
Include the new event handlers with the rest of the ActionScript for the panel, remembering to move onResize and setFaceColor into onFwStartMovie. Then publish it and test it in Fireworks. You should now be able to see the panel update with the correctly saved annotations text as you change selections within your Fireworks document (see Figure 10).
Often a panel requires that data specific to the panel itself, such as panel preferences, be saved and accessible for later use. Unlike annotation text, this cannot be saved to a specific Fireworks document because documents come and go. You need to save it instead to something more concrete like Fireworks itself. The problem is that you cannot save anything to Fireworks as you can to a Fireworks document. You can, however, save files to a user's hard drive and store the information there (see Figure 11).
The Annotations panel does this for the user name. It allows the user name to be set just once, preventing the user from having to reset it each time the panel is reopened or Fireworks is restarted. Although this is the only information required by the Annotations panel, any number of preferences may be required for other panels you may decide to develop.
The easiest way to save data for a panel is to save a text file from a panel using the Fireworks JavaScript command fw.saveJsCommand(). This saves a JSF file with the content you specify in a location you specify.
You could also use the Files object in Fireworks to write to a text file. However, this can be a little more complicated to deal with; and you get mostly the same effect with saveJsCommand. You also have more control over the extension (saveJsCommand forces you to save a file with a JSF extension). In either case, you get a text file saved with a string of data of your choice.
You can save such a data file in just about any format you want: XML, URL-encoded variable strings, or even JavaScript commands. If you are saving for the purpose of retrieving through Flash later—as is the case with most custom panels—then using XML or a URL-encoded variable string is usually the way to go. Saving as a JavaScript command would be useful if you were actually saving a command—something that would use Fireworks JavaScript to execute (for example, the fw.runScript() command can be used to run external JSF files).
The format for the Annotations panel is just a URL-encoded variable string. Because a single, simple name is being stored, using XML would be overkill. There's no good reason why anyone would want to edit this file manually (an advantage of using XML) so a variable string will work just fine.
Note: Even though saveJsCommand saves text as a JSF file, Flash can still load and interpret the file as XML.
As the panel developer, you would want to create an initial data file that provides default values. You can place it in the same folder as the panel SWF. For the Annotations panel, call it Annotations_user.jsf. Its initial contents will be the variable string for the user value:
user=Anonymous
Where you decide to save this file is actually up to you; it doesn't have to exist with the SWF, but it's usually easier that way. Just bear in mind that if you are dealing with JSF or SWF files in either the Commands or Command Panels folders in Fireworks, they may be interpreted as real commands or panels.
In Flash, this variable is accessed whenever the panel starts. This would be handled within an onFwStartMovie event. A new LoadVars instance, FWUser, can then load the file and retrieve the user name, adding it to the panel interface. It is from this instance that the user name will also be retrieved when using getUser as seen previously in setAnnotation. Complimenting getUser and setUser functions are defined as well:
var FWUser = new LoadVars();
function setUser(name){
FWUser.user = name;
user_label.text = "<b>User:</b> " + name;
}
function getUser(){
return FWUser.user;
}
FWUser.onLoad = function(success){
if (success && this.user){
setUser(this.user);
}else{
setUser("Unknown");
}
delete this.onLoad;
}
function onFwStartMovie(){
onFwActiveSelectionChange();
setFaceColor();
onResize();
FWUser.load("Annotations_user.jsf");
}
Create a Annotations_user.jsf file, if you haven't already, and update the Annotations FLA with the new code. When you publish it for Fireworks and test it, you should now see that the user indicates a name of "Anonymous" (see Figure 12). If there is a problem with the loading process, you might see "Unknown" instead.
To get the desired user name value from the actual Fireworks user, you can provide an input area in Flash. To make things easier, though, you can use a JavaScript prompt dialog box. The JavaScript prompt() command opens an input dialog box, which allows users to input a line of text that is then returned to the script as a string. For Flash, this can be easily accomplished using MMExecute. The Annotations panel uses prompt to get a name. When that name is retrieved, it is added to the interface and saved using saveJsCommand.
One thing about saveJsCommand is that, unlike the load method in the LoadVars class in Flash, it requires an absolute file path when saving. If none is provided, the file will be saved in the Fireworks Commands folder and misinterpreted as a usable, Fireworks command. Luckily, Fireworks provides variables in the fireworks (fw) object that specify folder locations within the Fireworks installation folder. The folder where the Annotations panel resides is the Command Panels folder. You can get its path using fw.appSwfCommandsDir.
Now you can make an editUser function that sets the user name and updates Annotations_user.jsf when the Edit button is clicked in the panel:
function editUser(){
var input = MMExecute('prompt("Set User Name:", unescape("' + escape(FWUser.user) + '"));');
if (input){
setUser(input);
MMExecute('fw.saveJsCommand("' + FWUser.toString() + '", fw.appSwfCommandsDir + "/Annotations_user");');
}
}
edituser_button.addEventListener("click", editUser);
Take note how escape and unescape are used with prompt. This is necessary when dealing with user input and values being sent from Flash into a MMExecute command. This is because you can't be entirely sure what the user has provided. For example, adding a quote in a user name will break the command when it is added to the MMExecute string. Using escape in ActionScript gets rid of these problematic characters, while unescape in JavaScript restores the string to its original state so that it is presented to the user properly in the prompt dialog box.
With that, the Annotations panel should be basically complete. Save your file and publish it to generate the completed panel SWF for Fireworks (see Figure 13).
Once you finish developing your panel, you still have one more task to complete: testing. Hopefully you've been testing while you were working with the panel, but it doesn't hurt to give it one more solid run-through before submitting to absolute completion. Also, as you use the panel in practice, you may find the need for additional features. For example, the Annotations panel could benefit from a new label that indicates what element the current annotation is being displayed for. This would be especially useful when multiple elements are selected even though only one annotation is being displayed (the first in the selection). Are you up to implementing that?
The following section provide additional suggestions to help with your panel development, which were not relevant to the Annotations example.
Macromedia provides a page for suggested Extension User Interface Guidelines. Although it's antiquated, it may be useful when designing your panels. It covers some useful guidelines, such as including a help button and brand placement or inserting a company logo.
As you begin to deal with larger Fireworks JavaScript commands within Flash, commands can become increasingly harder to add or edit. Commands used throughout this tutorial were fairly small. They were added line by line as strings to a single ActionScript variable. That variable was then used with MMExecute. This can get cumbersome when you deal with larger scripts. There are a couple of alternatives, however.
One alternative is to keep your scripts in an external JSF file. That way when you use fw.runScript(), you can run that script as you would any other command. You just have to be sure to correctly reference your file.
If you don't like the dependency on external files, you can also keep your scripts in Flash in the form of text within a text field. This way, you can edit your script more easily without worrying about defining it to a variable, because it will be accessible from the text field's text property.
We used a JavaScript prompt to request a string from the user and set the user name in the Annotations panel. Other JavaScript dialog boxes like alerts, confirmations, and even a yes/no dialog box are also at your disposal. Sometimes, though, you may want more functionality out of a dialog box. You can create a custom dialog box using Flash.
Fireworks commands can consist of simple Fireworks JavaScript commands or Flash SWF commands. When a command is a SWF, it plays the SWF in a pop-up dialog box that acts much like a JavaScript prompt. From a panel, you can launch a SWF command using fw.runScript. Unlike dialog boxes like prompts, however, SWF commands (or should I say fw.runScript) will not provide a return value when they are called. Instead, you need to create a variable in Fireworks that provides a means for the SWF command to obtain information and return that information back to the calling script. For example:
dialogObject = "value";
fw.runScript("mydialog.swf");
returnValue = dialogObject;
When mydialog.swf runs, it accesses the values provided in the dialogObject property through JavaScript. It then alters that variable to provide what will be assigned to returnValue. Its value reflects all choices made by the user while the dialog box is active.
Note: SWF commands called in this manner do not receive Fireworks events like panels do.
For external files that act as panel resources, you may want to add them to a Shared folder within the Fireworks Configuration folder:
Fireworks 8/Configuration/Shared/[your name]/[panel name]/
For example, if I didn't include the Annotations_user.jsf file with the Annotations SWF file, I would put it in the following folder:
Fireworks 8/Configuration/Shared/senocular/Annotations/
Doing so makes sure that these files are out of the way and not interpreted as additional commands or panels.
Flash can easily decode URL-encoded strings with the LoadVars class. Fireworks JavaScript has no such class, however. If you need to encoded or decode variables in Fireworks, you can use something like the following code, which recreates the LoadVars object within JavaScript (not including the loading capabilities):
function LoadVars(){}
LoadVars.prototype.decode = function(str){
var parts = str.split("&");
var sides;
for (var prop in parts){
sides = parts[prop].split("=");
if (sides.length > 1 && sides[0] != "encode" && sides[0] != "decode"){
this[sides[0]] = unescape(sides[1]);
}
}
}
LoadVars.prototype.encode = function(){
var str = "";
for (var prop in this){
if (prop != "encode" && prop != "decode"){
if (str) str += "&";
str += prop + "=" + this[prop];
}
}
return str;
}
Once you have successfully completed a panel and you're happy with what you got and want to share it with the world, the only thing left to do is distribute it. The best way to distribute an extension for Macromedia products, such as a Fireworks custom panel, is by distributing an MXP file to the Macromedia Extension Manager. An MXP file contains all the components of your extension along with instructions for the Extension Manager on how and where to install it.
| 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 |