fireworks
Command Dialog
For you Fireworks command developers out there who like to sling JavaScript but aren’t down with Flash, or just don’t want to deal with the overhead of building a SWF for a simple command, the Command Dialog library can help. It’s a combination of JS and Flex 3 that lets you create a SWF dialog using just JavaScript, so you can add a complex UI to your commands without any additional tools.
After installing the extension, you should have a “Command Dialog” subfolder in your Commands folder. The subfolder includes a few sample .jsf files that demonstrate how the library works. The “Command Dialog - Simple Example.jsf” file is thorougly documented and shows how a basic dialog can be quickly sepcified using JavaScript. You can jump in and play around with that, or read on for a basic introduction.
Loading the fwlib.dialog library
To use the library in your .jsf commands, copy the entire “lib” sub-folder to the folder that contains your .jsf. The folder hierarchy will look something like:
/Adobe Fireworks
/Configuration
/Commands
/My Command
My Command.jsf
/lib
...
At the top of your .jsf file, add this code:
try { dojo.require.call; } catch (exception)
{ fw.runScript(fw.currentScriptDir + "/lib/lib.js"); }
dojo.require("fwlib.dialog");
This checks whether the dojo library is already loaded, and loads it if not. It then tells dojo to load the fwlib.dialog library, which is the interface you’ll use for creating the Flex dialogs.
Opening a dialog
Once the library is loaded, you can open a modal dialog by calling fwlib.dialog.open(). Pass a single object to specify the attributes of the dialog:
fwlib.dialog.open({
title: "My First Dialog"
});
All that this example does is open an empty dialog. There’s no way to change the title of the dialog chrome at runtime, so it’ll always be the name of the SWF file, such as “Command Dialog - 500x375”. But the title you specify in the JS object will be used for the title of the Flex panel that’s rendered inside the window; in this case, “My First Dialog”.
If you’re on Windows, you can close the dialog simply by clicking the close box. On the Mac, the dialog that Fireworks opens doesn’t have a close box, for some reason, but you can just click in the dialog and press the escape key to close it.
Sizing the dialog
To specify a size for the dialog, set the size attribute of the dialog object to one of the following strings:
- 275x175
- 275x275
- 300x175
- 300x200
- 300x300
- 350x300
- 350x350
- 375x375
- 400x275
- 400x350
- 450x275
- 450x350
- 500x375
For example:
fwlib.dialog.open({
title: "My First Dialog",
size: "300x200"
});
This opens an empty dialog that’s 300px wide and 200px tall. Due to a limitation in how Fireworks opens Flash dialogs, it’s not possible to support arbitrary sizes. But it’s easy to add new fixed sizes to this list, so let me know which other sizes would be useful. The maximum size of a Flash dialog in Fireworks is 500x375. fwlib.dialog.open() will default to 500x375 if you don’t specify a size.
Specifying the dialog contents
Empty dialogs aren’t very interesting, of course, so here’s an example that includes an actual clickable Flex control:
fwlib.dialog.open({
title: "My First Dialog",
size: "300x200",
children: [
{ Button: {
name: "OKBtn",
label: "OK",
width: 75
} }
]
});
The dialog’s child elements are included in its children array. Currently, the following Flex 3 element classes are supported:
- Box
- Button
- CheckBox
- ColorPicker
- ComboBox
- ControlBar
- DateChooser
- DateField
- DividedBox
- Form
- FormHeading
- FormItem
- HBox
- HDividedBox
- HRule
- HSlider
- Image
- Label
- List
- NumericStepper
- Panel
- RadioButton
- Spacer
- TabNavigator
- Text
- TextArea
- TextInput
- TileList
- ToggleButtonBar
- VBox
- VDividedBox
- VRule
- VSlider
Each element is specified with an object that has a single attribute, the name of which identifies the Flex element class, such as Button. The value of this attribute is another object, which contains the element’s properties. For instance, { Button: {} } would create a button with no label -- not very useful, but it highlights the inner and outer object structure. You’d specify the button’s label as an attribute on the inner object: { Button: { label: “My Button” } }. When closing an element object, don’t forget the double braces. Your script won’t run at all if you miss one.
A more complicated example using a NumericStepper control looks like:
fwlib.dialog.open({
title: "My First Dialog",
size: "300x200",
children: [
{ NumericStepper: {
name: "XValue",
value: 10,
stepSize: 1,
maximum: 100000,
minimum: -100000,
toolTip: "Pixels to move horizontally"
} },
{ Button: {
name: "OKBtn",
label: "OK",
width: 75
} }
]
});
Although you don’t need to know Flex or ActionScript to use the fwlib.dialog library, you’ll need to reference the Flex 3 docs for details on the properties and styles that each element supports: http://livedocs.adobe.com/flex/3/langref/
Ignore the MXML syntax in the documentation and refer instead to the Public Properties section for each class. In MXML, all the property values are specified with strings, but with the fwlib.dialog library, you’ll need to set the properties to values of the appropriate type. In the example above, for instance, you’d say stepSize=”1” in MXML, but since that property requires a number, you’d use stepSize: 1 when specifying it with JavaScript.
Note that not every property will necessarily be useful in a dialog, since you can’t script the elements using ActionScript and features like data binding aren’t supported. But most of the basic properties and styles should behave as described in the documentation.
To size an element as a percentage of its container, use the percentWidth and percentHeight properties. Setting width to a string like “100%” won’t work.
There is also a special non-Flex property called _focused that you can set to true on the element that you want to have keyboard focus when the dialog opens. This can make it easier for the user to quickly enter a value.
Creating an element hierarchy
For container types, like HBox and ControlBar, you can add child elements via their children array, like:
fwlib.dialog.open({
title: "My First Dialog",
size: "300x200",
children: [
{ HBox: {
children: [
{ Label: {
text: "X:"
} },
{ NumericStepper: {
name: "XValue",
value: 10,
stepSize: 1,
maximum: 100000,
minimum: -100000,
toolTip: "Pixels to move horizontally"
} }
]
} },
{ ControlBar: {
children: [
{ Button: {
name: "OKBtn",
label: "OK",
width: 75
} }
]
} }
]
});
Of course, these child elements can have their own children, and so on.
Confirm and dismiss buttons
In the examples above, clicking the OK button doesn’t actually do anything. To specify which button should confirm the dialog and which should cancel it, set the confirmButton and dismissButton properties to the names of the corresponding button elements, like:
fwlib.dialog.open({
title: "My First Dialog",
size: "300x200",
confirmButton: "OKBtn",
dismissButton: "CancelBtn",
children: [
{ HBox: {
children: [
{ Label: {
text: "X:"
} },
{ NumericStepper: {
name: "XValue",
value: 10,
stepSize: 1,
maximum: 100000,
minimum: -100000,
toolTip: "Pixels to move horizontally"
} }
]
} },
{ ControlBar: {
children: [
{ Button: {
name: "OKBtn",
label: "OK",
width: 75
} },
{ Button: {
name: "CancelBtn",
label: "Cancel",
width: 75
} }
]
} }
]
});
Then, make sure you have buttons named “OKBtn” and “CancelBtn” somewhere in the dialog. The buttons in this example are contained within a ControlBar element, which automatically positions them at the bottom of the dialog, but this isn’t required.
You can also have more than one confirm or dismiss button. For example, you might want OK, Copy and Cancel buttons, with either the OK or Copy button confirming the dialog. In that case, set confirmButton to an array containing the names of the buttons. Which button the user clicked to confirm or cancel the dialog will be returned in the _closeButton property of the object returned from the open method, which is described in the next section.
Returning values
Of course, all of this would be pointless if your JavaScript can’t see what the user entered in the Flex dialog. fwlib.dialog.open() returns an object if the user clicks the confirm button or presses enter and it returns null if the user clicks the dismiss button or presses escape. You can use it much like the built-in Fireworks prompt() function:
var result = fwlib.dialog.open({
title: "My First Dialog",
size: "300x200",
confirmButton: "OKBtn",
dismissButton: "CancelBtn",
children: [
{ NumericStepper: {
name: "XValue",
value: 10,
...
If the user clicks OK in this example, result will be an object that contains a property for each named element in the dialog (regardless of where it appears in the object hierarchy). So result.XValue (the name of the NumericStepper element) will contain the number that the user had entered. Make sure all of your element names are unique within the dialog. Static elements like HRule or VBox don’t need to be named.
The values of DateField and DateChooser elements are JavaScript Date objects, or null if the user didn’t enter anything. The value of a ColorPicker element is a string containing a CSS color, like “#ff0000”.
Styling elements
Most Flex elements also support various styles, like fontSize. These need to be specified on a style: {...} property, rather than directly on the inner properties object.
fwlib.dialog.open({
size: "300x200",
children: [
{ Label: {
text: "This is really big, ugly text.",
style: {
fontWeight: "bold",
fontStyle: "italic",
fontSize: 18,
color: "0xff0000"
}
} }
]
});
You must use the camelCase names for the style properties, unlike the pseudo-CSS markup supported in MXML files. Note that color values are not specified with CSS color strings but hex values, like “0xff0000”.
Instead of tweaking the styles on individual elements, you can also specify global styles on a css property on the dialog object.
fwlib.dialog.open({
size: "300x200",
css: {
"Label": {
fontWeight: "bold",
fontStyle: "italic",
fontSize: 18,
color: "0xff0000"
}
},
children: [
{ Label: {
text: "This is really big, ugly text."
} },
{ Label: {
text: "And here's some more!"
} }
]
});
In the example above, the default style for Label elements is specified with the “Label” selector in the css property. This changes the appearance of all Label elements in the dialog.
See the “Command Dialog - Complex Example.jsf” file for more examples of using styles.
Handling events
In a simple dialog, you may just be asking the user to enter some information, so all you need when the dialog closes is the list of entered values. But in more complex commands, you may want to update the dialog in response to user interaction. In a normal Flex application, you’d write these event handlers in ActionScript. With the fwlib.dialog library, however, you can write them in JavaScript.
To create an event handler for an element, add an events: {} property to it and add a function to events with the same name as the Flex event you want to handle. You can add as many event handlers as you like. For example:
fwlib.dialog.open({
size: "300x200",
children: [
{ TextInput: {
name: "Foo",
events: {
change: function(event)
{
alert(event.currentValues.Foo);
}
}
} }
]
});
This displays an alert every time you change the text in the input field. See the Flex 3 documentation for a list of the events that each element supports.
The event handler is called with a single object that has the following properties:
| type | The name of the event that triggered the handler. In the example above, event.type would be “change”. |
| targetName | The name of the element that triggered the event. In the example above, event.targetName would be “Foo”. |
| currentValues | An object containing the current values of all the elements in the dialog. In the example above, event.currentValues.Foo would be whatever the user had typed up to that point. |
Now, getting the Flex dialog to update in response to one of these events is tricky, since the Flash player doesn’t include an ActionScript compiler and therefore has no way to run an arbitrary block of AS. But it’s possible to set the result property of the event object to an array of “statements” that can modify elements in the dialog. It’s basically a poor-man’s form of scripting.
Each statement is an array of at least three items. The first is the name of the element to affect. The second is the name of the element’s property to set or its method to call. The third item is the value to set the property to or the first parameter of the method call (if the method doesn’t take any parameters, the statement array can have just two items). Any remaining items in the array will also be passed as parameters, but will be ignored if the second parameter is a property.
An example will hopefully make all this a little clearer. the following code echoes the text from the Foo input field to the FooEcho field whenever Foo changes:
fwlib.dialog.open({
size: "300x200",
children: [
{ TextInput: {
name: "Foo",
events: {
change: function(event)
{
event.result = ["FooEcho", "value",
event.currentValues.Foo];
}
}
} },
{ TextInput: {
name: "FooEcho"
} }
]
});
The array [“FooEcho”, “value”, event.currentValues.Foo] is equivalent to the ActionScript statement FooEcho.value = event.currentValues.Foo.
You can also set the event’s result property to an array of arrays, in order to execute a series statements.
fwlib.dialog.open({
size: "300x200",
children: [
{ TextInput: {
name: "Equation",
_focused: true,
events: {
change: function(event)
{
var equation = event.currentValues.Equation;
try {
event.result = [
["Result", "text", equation ?
eval(equation) : ""],
["Equation", "setStyle",
"backgroundColor", 0xffffff]
];
} catch (e) {
event.result = [
["Result", "text", ""],
["Equation", "setStyle",
"backgroundColor", 0xffaaaa]
];
}
}
}
} },
{ TextInput: {
name: "Result",
editable: false
} }
]
});
In the example above, every time the Equation element changes, its text value is evaluated. If the string throws an exception when eval’d, the Result element is cleared and the background color of the Equation field is set to red. Otherwise, the result of the eval is displayed in Result and the Equation background is set to white. Note that setStyle is a method, so the equivalent ActionScript looks like Equation.setStyle(“backgroundColor”, 0xffaaaa).
Although the syntax is a little awkward, using these statement arrays in an event handler does give you a fair degree of control over the state of the dialog elements. The “Move Selection” command included in the extension, for example, implements Illustrator’s Move Selection dialog. The user can enter either an X and Y value or a distance and an angle. In either case, the other fields are updated to stay in sync, using the technique described above. You could also use the statements to enable or disable certain controls when the user clicks a radio button, change the color of an element when they move a slider, and so on.
Note that due to an obscure but highly annoying bug in Fireworks, if the event handler calls a JavaScript function that returns a value, a modal “Processing command script” dialog will appear on top of the dialog. You’re basically hosed at this point, since clicking Cancel in the dialog does nothing. You just have to kill Fireworks. (This bug is why you have to set the result property on the event object to specify the statements, rather than just returning them from the event handler.)
Technical details
It’s not important to know the technical details in order to use the fwlib.dialog library, but for the curious, here’s a basic outline. When you pass a JavaScript object to fwlib.dialog.open(), it turns your object into a JSON string, using the dojo JSON library, and stores it on the fwlib.dialog object. It then calls fw.runScript() on the SWF that corresponds to the size of dialog you specified, e.g., “Command Dialog - 300x200.swf”. These SWFs are just barebones classes that load another file, “JSONDialog.swf”, which contains all the AS3 code. This way, each “Command Dialog” SWF is just 875 bytes, instead of the full 369K of “JSONDialog.swf”.
When the “JSONDialog.swf” file loads, it calls back to the JavaScript to access the dialog’s JSON data and uses that to render the dialog. The process is basically similar to what the Flex compiler does: a source document (MXML in the normal Flex case, JSON here) is parsed, and then a tree of UIComponentDescriptor objects corresponding to the source document is generated. Normally the code to instantiate these UIComponentDescriptor objects is generated automatically by the Flex compiler, but they can also be created programmatically at runtime. Once they are, createComponentFromDescriptor() is called on the Application object to instantiate each Flex UI object (Button, NumericStepper, etc.), and then validateNow() is called to display the elements onscreen.
While the dialog is open, Flex handles the user interaction. If you had created any event handlers for your dialog, the JS functions are stripped out of the JSON, since they can’t run in the Flash player. Instead, the AS3 code calls back to fwlib.dialog when an event is supposed to be handled, and the JS code dispatches it to your event handler. If the handler sets a result on the event object, that result is converted to JSON and made available on fwlib.dialog, where the AS3 code can retrieve it. The “statements” in the result array are then applied to the Flex elements in the dialog.
If the user confirms the dialog, the AS3 code runs through every named element in the dialog and stores its value in an object, which is then returned to your code as the result of the fwlib.dialog.open() call.
Release history
0.1.0: Initial release.
Package contents:
- Command Dialog Example - Complex
- Command Dialog Example - Simple
- Move Selection
- Command Dialog - 275x175
- Command Dialog - 275x275
- Command Dialog - 300x175
- Command Dialog - 300x200
- Command Dialog - 300x300
- Command Dialog - 350x300
- Command Dialog - 350x350
- Command Dialog - 375x375
- Command Dialog - 400x275
- Command Dialog - 400x350
- Command Dialog - 450x275
- Command Dialog - 450x350
- Command Dialog - 500x375
- JSONDialog