fireworks

JSON Panel

For you Fireworks 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 panel, the JSON Panel library can help.  It’s a combination of JS and Flex 3 that lets you create a Flash panel using just JavaScript, so you can build a fully functional Fireworks panel with nothing more than a text editor.

After installing the extension, you should have two files in the Command Panels folder: JSONPanel.swf and JSONPanel.js.  You’ll need to restart Fireworks to get the new panel to appear in the Window menu.  The “JSONPanel.js” file is thorougly documented and shows how a basic panel can be quickly specified using JavaScript.  You can jump in and play around with that, or read on for a basic introduction.

Registering a panel

To build a new panel with the library, make a copy of the JSONPanel.swf and JSONPanel.js files in the Command Panels folder, and give the copies the same base name.  For example, if you want to create a panel called “My Panel”, the folder hierarchy should look like:

/Adobe Fireworks
/Configuration
/Command Panels
My Panel.swf
My Panel.js

After creating the copies, you’ll need to restart Fireworks for the panel to appear in the Window menu.

The SWF file contains all the logic for rendering the panel.  You won’t modify this file, but each panel you create needs its own copy of it, since Fireworks scans the Command Panels folder for SWFs and displays a menu item for each one it finds.

When the SWF is first opened, it will look in its folder for a .js file with the same name as the SWF.  If it doesn’t find the file, the panel will be blank.

To specify the structure of the panel, your .js file needs to call fwlib.panel.register() with a single object containing the panel’s properties:

fwlib.panel.register({});

This example doesn’t display anything, but it’s the bare minimum code for building a panel.  You don’t need to specify the name of the SWF when calling register since the SWF executes your .js file and it knows its own filename.

Specifying the panel UI

Empty panels aren’t very interesting, of course, so here’s an example that includes an actual clickable Flex control:

fwlib.panel.register({
children: [
{ Button: {
label: "Do It"
} }
]
});

When you change the .js file, the panel won’t automatically update.  You can close and reopen the panel or just click inside it and then press F5, which immediately reloads the JavaScript.  This makes working on a panel as iterative as refreshing a webpage in a browser.

The panel’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.panel.register({
children: [
{ NumericStepper: {
name: "XValue",
value: 10,
stepSize: 1,
maximum: 100000,
minimum: -100000,
toolTip: "Pixels to move horizontally"
} },
{ Button: {
label: "Move"
} }
]
});

Although you don’t need to know Flex or ActionScript to use the fwlib.panel 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.panel 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 panel, 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.

Creating an element hierarchy

For container types, like HBox and ControlBar, you can add child elements via their children array, like:

fwlib.panel.register({
children: [
{ HBox: {
children: [
{ Label: {
text: "X:"
} },
{ NumericStepper: {
name: "XValue",
value: 10,
stepSize: 1,
maximum: 100000,
minimum: -100000,
toolTip: "Pixels to move horizontally"
} }
]
} },
{ HBox: {
children: [
{ Label: {
text: "Y:"
} },
{ NumericStepper: {
name: "YValue",
value: 10,
stepSize: 1,
maximum: 100000,
minimum: -100000,
toolTip: "Pixels to move vertically"
} }
]
} }
]
});

Of course, these child elements can have their own children, and so on.

Handling events

Unlike the Command Panel extension, which works similarly to this library, it’s not enough to simply render a set of interface elements into which the user can enter values.  You have to give the user a way to act on that input, and the only way to do that is to handle the Flex events generated by the elements.

In a normal Flex application, you’d write these event handlers in ActionScript.  With the fwlib.panel 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.panel.register({
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:

typeThe name of the event that triggered the handler.  In the example above, event.type would be “change”.
targetNameThe name of the element that triggered the event.  In the example above, event.targetName would be “Foo”.
currentValuesAn object containing the current values of all the named elements in the panel.  In the example above, event.currentValues.Foo would be whatever the user had typed up to that point.  Remember that if you want to be able to access an element’s value in your event handler, you must give it a unique name.  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”.

Typically, you’ll want to change the state of the Fireworks document when the user interacts with your panel, such as by clicking a button.  In your event handler, you have the full Fireworks API at your disposal.  One of the major advantages of the JSON Panel library over creating a panel in Flash is that instead of concatenating a long string of JavaScript code in ActionScript to pass to the MMExecute() function, you don’t have to worry about escaping your code at all, since you’re writing JS rather than AS.  Just write it as you would any other Fireworks command.

In the following example, clicking the Move button moves the current selection by the amount specified in the NumericStepper fields:

fwlib.panel.register({
children: [
{ HBox: {
children: [
{ Label: {
text: "X:"
} },
{ NumericStepper: {
name: "XValue",
value: 10,
stepSize: 1,
maximum: 100000,
minimum: -100000,
toolTip: "Pixels to move horizontally"
} }
]
} },
{ HBox: {
children: [
{ Label: {
text: "Y:"
} },
{ NumericStepper: {
name: "YValue",
value: 10,
stepSize: 1,
maximum: 100000,
minimum: -100000,
toolTip: "Pixels to move vertically"
} }
]
} },
{ Button: {
label: "Move",
events: {
click: function(event)
{
fw.getDocumentDOM().moveSelectionBy(
{ x: event.currentValues.XValue,
y: event.currentValues.YValue },
false,
false
);
}
}
} }
]
});

Updating the panel UI

Now, getting the Flex panel 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 at runtime.  But it’s possible to set the result property of the event object to an array of “statements” that can modify elements in the panel.  It’s basically a poor-man’s form of scripting.

Each statement is an array of at least two 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, if any, is the value to set the property to or the first parameter of the method call.  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.panel.register({
children: [
{ TextInput: {
name: "Foo",
events: {
change: function(event)
{
event.result = ["FooEcho", "text",
event.currentValues.Foo];
}
}
} },
{ TextInput: {
name: "FooEcho",
editable: false
} }
]
});

The array [“FooEcho”, “text”, event.currentValues.Foo] is equivalent to the ActionScript statement FooEcho.text = 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.panel.register({
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 panel elements.  For example, you can 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.  Remember that any element you want to be able to manipulate with these statements must have a unique name.  Static elements like HRule or VBox generally don’t need to be named.

Handling Fireworks events

In addition to the events generated when the user interacts with the Flex controls, Fireworks itself fires events when the state of the application changes.  For instance, it generates an event when the selection changes, when the current document changes, when the zoom level changes, and so on.

The following Fireworks events are supported:

  • onFwActiveDocumentChange
  • onFwActiveSelectionChange
  • onFwActiveToolChange
  • onFwActiveToolParamsChange
  • onFwActiveViewChange
  • onFwApplicationActivate
  • onFwApplicationDeactivate
  • onFwCurrentFrameChange
  • onFwCurrentLayerChange
  • onFwDocumentClosed
  • onFwDocumentNameChange
  • onFwDocumentOpen
  • onFwDocumentSave
  • onFwDocumentSizeChange
  • onFwFavoritesChange
  • onFwHistoryChange
  • onFwObjectSettingChange
  • onFwPixelSelectionChange
  • onFwPreferencesChange
  • onFwStartMovie
  • onFwStopMovie
  • onFwSymbolLibraryChange
  • onFwUnitsChange
  • onFwURLListChange
  • onFwZoomChange

The Cross Products Extensions > Flash Panels > Events section of the “Extending Adobe Fireworks CS4” documentation describes when each of these events is triggered.

To handle one of these Fireworks events, add an events: {} property to the top level of the panel object and add a function to events with the same name as the event you want to handle.  You can add as many event handlers as you like.

In this example, the panel updates a label every time the user switches to a different tool in the toolbox:

fwlib.panel.register({
events: {
onFwActiveToolChange: function(event)
{
event.result = ["ToolName", "text", fw.activeTool];
}
},
children: [
{ Label: {
name: "ToolName",
text: fw.activeTool
} }
]
});

Your event handler is called with a single object that has the following properties:

typeThe name of the event that triggered the handler.  In the example above, event.type would be “onFwActiveToolChange”.
currentValuesAn object containing the current values of all the named elements in the panel.

Unlike Flex events, there is no targetName property in this object, since an element is not generating the event.

One of the challenges with writing panels in Fireworks is that it often generates a large number of events, which can slow the application down.  When the user edits text, for instance, Fireworks generates onFwActiveToolChange events constantly.  For that reason, your panel will not receive any events while the text tool is selected in the toolbox (even if the user is not currently editing text).  So in the example above, you won’t see the panel display “Text” as the current tool.

An event you’ll often want to handle is onFwStartMovie, which is called when the panel is first loaded.  You can handle this event to initialize the state of the panel.

Another common event is onFwActiveSelectionChange, which fires when the selection changes.  You can use this to update the panel to show information about the selection, such as its position or size.  Unfortunately, this event isn’t generated every time you might need to update your panel.  For instance, it doesn’t fire when the user switches to a different document, even though the current selection they’re working with has changed.  Nor does it fire when the user uses the arrow keys to move the selection around, even though its position has changed.

To work around this, you may want to handle multiple events using the same function.  In the following example, one function is used to handle 4 events by specifying the same function multiple times in the panel’s events property.  The onSelectionChange function displays the selection’s current position in a Label element:

function onSelectionChange(event)
{
var dom = fw.getDocumentDOM();
var x = "", y = "";

if (dom && fw.selection.length) {
var bounds = dom.getSelectionBounds();
x = bounds.left;
y = bounds.top;
}

event.result = ["Position", "text",
(x != "") ? (x + ", " + y) : ""];
}

fwlib.panel.register({
events: {
onFwStartMovie: onSelectionChange,
onFwActiveSelectionChange: onSelectionChange,
onFwObjectSettingChange: onSelectionChange,
onFwActiveDocumentChange: onSelectionChange
},
children: [
{ Label: {
name: "Position"
} }
]
});

When onSelectionChange is called to handle onFwStartMovie, it immediately updates the panel with the position of the current selection, if any.  It also updates the panel when the selection changes (onFwActiveSelectionChange), when it’s moved via the keyboard (onFwObjectSettingChange) or when it changes because the user’s switched to a different document (onFwActiveDocumentChange).

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.panel.register({
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 numeric values, like “0xff0000”.

In addition to tweaking the styles on individual elements, you can also specify global styles on a css property on the panel object.

fwlib.panel.register({
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 panel.

See the “JSONPanel.js” file for more examples of using styles.

Technical details

It’s not important to know the technical details of how all this works in order to use the fwlib.panel library, but for the curious, here’s a basic outline.  Unlike the Command Dialog library, where your .jsf file launches the .swf, the JSON Panel library works the opposite way.  So when your copy of the JSONPanel.swf file is started, it loads the fwlib.panel API into the global JavaScript environment.  It then executes the .js file with the same as the SWF (JSONPanel.js, in this case).

When you pass a JavaScript object to fwlib.panel.register() in the .js file, it associates that object with the SWF file.  The SWF then calls back to get the object you specifed as a JSON string, and uses that to render the panel.  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 panel is open, Flex handles the user interaction.  If you had created any event handlers for your panel, 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.panel 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 returned to the AS3 code.  The “statements” in the result array are then applied to the Flex elements in the panel.

Release history

0.1.0: Initial release.

Package contents

  • JSONPanel

See all the Adobe Fireworks extensions, commands and panels