19 November 2007
To benefit most from this series, it is best if you have a basic working knowledge of Flex.
Intermediate
Note: This article was created based on the Flex 3 beta releases. Minor changes in the description and code may be necessary before it can be applied to Flex 3.
Two-state buttons (push-on, push-off) are a fairly common component, and Adobe Flex addresses these simple needs with both the button and check box controls. Recently I needed an icon-only version, and also needed to create multiple versions of the button.
This article shows how I learned to create a custom component based on the Flex checkbox component, and some of the tricks I learned along the way.
Whenever I write applications, I first sit down with a pencil and paper (yes, I do—literally) and sketch out what I want the finished product to look like, based on what I know it should do. The upside of this drawing is that I don't get distracted by user interface components that might be easy or fun, and instead work towards the intended functionality. The downside is that I often wind up "coloring outside the lines"—and having to look for interesting ways of accomplishing what I want to do, but it is not often what the development environment provides. I'll assume this is also your preferred method—so now, both of us are wandering into new territory.
I created the Creative Suite Plug-in Browser for customers who were looking for a solution—and I knew it needed to allow a user to search by Adobe product. I wanted to create a push button with the Adobe product icon. When a user selects an icon in the control, the icon is grayed-out, and any plug-in that only works with that Adobe product no longer displays in the list. When the user selects the icon again by clicking it, the product list display is updated. I thought that a push button with icons would be attractive, an effective use of space, good Adobe branding, and so on. Just to prove that it can be done, look at Figure 1.
Lucky me! Flex check boxes are built to be reskinned. Simply create 10 icons and I'm done!
First, I quickly created 10 check boxes. Here's the code for the first one:
<mx:CheckBox x="280" y="112" label="aiCB" width="60" height="60">
<mx:upIcon>@Embed('file:////Users/mnr/Documents/adobe CS3 logos/ai_appicon_disabled.jpg')</mx:upIcon>
<mx:overIcon>@Embed('file:////Users/mnr/Documents/adobe CS3 logos/ai_appicon_disabled.jpg')</mx:overIcon>
<mx:downIcon>@Embed('file:////Users/mnr/Documents/adobe CS3 logos/ai_appicon_disabled.jpg')</mx:downIcon>
<mx:selectedUpIcon>@Embed('file:////Users/mnr/Documents/adobe CS3 logos/ai_appicon.jpg')</mx:selectedUpIcon>
<mx:selectedOverIcon>@Embed('file:////Users/mnr/Documents/adobe CS3 logos/ai_appicon.jpg')</mx:selectedOverIcon
<mx:selectedDownIcon>@Embed('file:////Users/mnr/Documents/adobe CS3 logos/ai_appicon.jpg')</mx:selectedDownIcon
<mx:selected>true</mx:selected>
<mx:width>60</mx:width>
<mx:height>60</mx:height>
<mx:change>promoterbotArray.refresh()</mx:change>
</mx:CheckBox>
Most of this code supplies the proper icons for the various states of the check box. There are also width/height tags, and a brief chunk of code that refreshes the list anytime the button changes state.
Now do that nine more times. But wait—what's that alarm I hear? Oh yes, it's the "crappy, non-reusable-code alarm."
Experienced coders will point out several things about this method:
What I really needed was to create some sort of template for this check box, and then apply changes to it. In normal JavaScript, you would typically build a function that dynamically builds a component using a set of parameters. You might even build an object and create instances of a new check box. Flex provides a really easy way of doing this called a component. Components are a lot like objects—except easier to work with. No instantiation needed: just code and go!
The Flex help files have a great description on creating custom components. I'll abbreviate the steps here:
namespace tag to the Application tag at the very beginning of the code. Here's the code before and after. Before:
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute">
After:
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml/2006/mxml" xmlns:mnr="mnrComponents.*" layout="absolute" >
Your mx:Application tag might have other tags in it, but the important thing is that the xmlns:mnr has connected the main code with the new component. In my example, mnr is the new namespace (it can be anything that makes sense to you) and mnrComponents.* links your main.mxml with any files located in the mnrComponents directory.
When you are ready to use your new, personalized check box, you identify it with the new namespace you just created. Instead of using <mx:CheckBox>, you would use <mnr:iconCheckbox>. If you don't do anything to the component you defined in the first step above, your new check box will behave identically to the normal Flex check boxes.
But: I have big plans for this little check box…
Most importantly, I wanted each check box to use custom icons—and I wanted to specify only the "Up" icon and the "Down" icon. I wanted the component to handle all the messy assignments to the upIcon, overIcon, downIcon, and so forth.
Passing parameters to a component is easy; I'll leave it up to you to refer to the instructions in the help file. However, passing icons proved to be somewhat tricky. I tried passing the @embed data—with no luck. I tried passing the string, but components won't embed images. It turns out you little choice but to put on the ActionScript hat.
I finally solved this with two main steps: Create a collection of "embeds" and assign them to variables, and then pass the variables to the check box component, shown in the following section.
First, create an ActionScript file (File > New > ActionScript File), name it adobeProductIcons.as or whatever makes sense to you, and stash it in a project folder (I used "Actionscript_includes"). Connect it to your main.mxml with the following line, placed immediately after the <mx:Application...> tag:
<mx:Script source="Actionscript_includes/adobeProductIcons.as" />
Inside of adobeProductIcons.as, embed and bind the icons. It will look something like the following:
// Illustrator icons
[Embed(source=file:////Users/mnr/Documents/adobeLogos/ai_appicon_disabled.jpg")]
[Bindable]
public var aiIconDisabled:Class;
[Embed(source=file:////Users/mnr/Documents/adobeLogos/ai_appicon.jpg")]
[Bindable]
public var aiIcon:Class;
Do this for each disabled/enabled icon pair, once for each check box. Save it and close it.
You'll need to create some ActionScript variables to pass the icon data to the component. At the top of iconCheckbox.mxml, right after the <mx:Application> tag, add the following:
<mx:Script>
<![CDATA[
[Bindable]
public var theActiveIcon:Class;
[Bindable]
public var theDisabledIcon:Class;
]]>
</mx:Script>
This creates two variables: theActiveIcon and theDisabledIcon. Once you declare those variables, you can use them in iconCheckbox.mxml to handle the messy process of skinning the check box. Add this code right after the <mx:script> tag you added above:
<mx:upIcon>{theDisabledIcon}</mx:upIcon>
<mx:overIcon>{theDisabledIcon}</mx:overIcon>
<mx:downIcon>{theDisabledIcon}</mx:downIcon>
<mx:selectedUpIcon>{theActiveIcon}</mx:selectedUpIcon>
<mx:selectedOverIcon>{theActiveIcon}</mx:selectedOverIcon>
<mx:selectedDownIcon>{theActiveIcon}</mx:selectedDownIcon>
These buttons are always the same size, so you can add the following code to the component:
<mx:width>60</mx:width>
<mx:height>60</mx:height>
This provides a little bonus. If you want to change the size of the icons, change the values in iconCheckbox.mxml—one change, instead of having to change each individual check box. Keep in mind that doing something like this makes this component unique to your application ("tightly coupled") because it assumes the check boxes made with this component always have a width and height of 60. I'm violating reusability guidelines in favor of this particular shortcut.
In your main.mxml, you can see how this works. Originally, each check box required the following code:
<mx:CheckBox x="280" y="112" label="aiCB" width="60" height="60">
<mx:upIcon>@Embed('file:////Users/mnr/Documents/adobe CS3 logos/ai_appicon_disabled.jpg')</mx:upIcon>
<mx:overIcon>@Embed('file:////Users/mnr/Documents/adobe CS3 logos/ai_appicon_disabled.jpg')</mx:overIcon>
<mx:downIcon>@Embed('file:////Users/mnr/Documents/adobe CS3 logos/ai_appicon_disabled.jpg')</mx:downIcon>
<mx:selectedUpIcon>@Embed('file:////Users/mnr/Documents/adobe CS3 logos/ai_appicon.jpg')</mx:selectedUpIcon>
<mx:selectedOverIcon>@Embed('file:////Users/mnr/Documents/adobe CS3 logos/ai_appicon.jpg')</mx:selectedOverIcon>
<mx:selectedDownIcon>@Embed('file:////Users/mnr/Documents/adobe CS3 logos/ai_appicon.jpg')</mx:selectedDownIcon>
<mx:selected>true</mx:selected>
<mx:width>60</mx:width>
<mx:height>60</mx:height>
<mx:change>promoterbotArray.refresh()</mx:change>
</mx:CheckBox>
Now, you can do this instead:
<mnr:iconCheckbox id="aiCB" theActiveIcon="{aiIcon}" theDisabledIcon="{aiIconDisabled}">
<mx:change>promoterbotArray.refresh()</mx:change>
</mnr:iconCheckbox>
This new code just makes my heart sing! I've removed about 90 lines of code from main.mxml, and my peers aren't laughing at me nearly as loud as before.
But there's still that pesky <mx:change>promoterbotArray.refresh()</mx:change> line that needs to be repeated for each check box. Briefly, it tells the promoterbotArray that it needs to filter out the products that shouldn't be listed. Look up "DataGrid Control" in the help topics for more details. There's a lot more to it, but that's another story. Briefly, I originally tried to place it just in iconCheckbox.mxml but found that the component didn't know what I was talking about. Quelle est cette promoterbotArray, vous demandez? It turns out that since promoterbotArray was defined in main.mxml, it was "out of scope" for the component—kind of like looking for your socks in the refrigerator: you have to know where to look to find stuff.
It took a bit of digging but it turns out that components are actually nested inside of the main code. If you are familiar with JavaScript, you know that you can change scope by using the DOM addressing model. Likewise for components in Flex—with a catch. In most of the Flex documentation, the outerDocument property is used to access objects defined outside of a component. Seems logical—but it didn't work. It wasn't until I discovered parentDocument that the component worked.
I added this line to iconCheckbox.mxml and, hey-presto, it works like a charm!
<mx:change>parentDocument.promoterbotArray.refresh()</mx:change>
Of course, this really violates reusability guidelines. I've now written a component that assumes there is a particular array located in a specific location. It might be better to leave this as part of the check box definition in the "main.mxml" code. Again, I've made a tradeoff between reusability and what I view as simplicity.
It may seem like a long trip, but this has a lot of benefits. In this case, I started off with this…
<mx:CheckBox x="280" y="112" label="aiCB" width="60" height="60">
<mx:upIcon>@Embed('file:////Users/mnr/Documents/adobe CS3 logos/ai_appicon_disabled.jpg')</mx:upIcon>
<mx:overIcon>@Embed('file:////Users/mnr/Documents/adobe CS3 logos/ai_appicon_disabled.jpg')</mx:overIcon>
<mx:downIcon>@Embed('file:////Users/mnr/Documents/adobe CS3 logos/ai_appicon_disabled.jpg')</mx:downIcon>
<mx:selectedUpIcon>@Embed('file:////Users/mnr/Documents/adobe CS3 logos/ai_appicon.jpg')</mx:selectedUpIcon>
<mx:selectedOverIcon>@Embed('file:////Users/mnr/Documents/adobe CS3 logos/ai_appicon.jpg')</mx:selectedOverIcon>
<mx:selectedDownIcon>@Embed('file:////Users/mnr/Documents/adobe CS3 logos/ai_appicon.jpg')</mx:selectedDownIcon>
<mx:selected>true</mx:selected>
<mx:width>60</mx:width>
<mx:height>60</mx:height>
<mx:change>promoterbotArray.refresh()</mx:change>
</mx:CheckBox>
…times 10—one for each button—cluttering up main.mxml. After all the component work, creating a check box now takes one line:
<mnr:iconCheckbox id="cbAI" theActiveIcon="{aiIcon}"
theDisabledIcon="{aiIconDisabled}" />
If you are going to create custom components, be sure to read the Adobe Flex documentation chapter on creating MXML components. Especially take a look at the chapter titled "About reusable MXML components"—it explains the difference between tightly coupled and reusable components, and when they are appropriate.

This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 3.0 Unported License