Mark Niemann-Ross

Created

20 November 2007

Requirements

Prerequisite knowledge
To benefit most from this series, it is best if you have a basic working knowledge of Flex.
 
User level
Intermediate
Required products
Flash Builder (Download trial)
Sample files
plugin_browser.zip (1843 KB)
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.
 

 
Getting started

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.
 
Figure 1. Adobe Creative Suite Plug-in Browser
Figure 1. Adobe Creative Suite Plug-in Browser

Lucky me! Flex check boxes are built to be reskinned. Simply create 10 icons and I'm done!
 

 
Using the crowbar approach

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:
 
  • If you change the pathname of the icons, you'll face 60 corrections.
  • If you change how the buttons work, you'll face 10 corrections.
  • This is going to make messy code that is difficult to read and maintain.
  • Most importantly, your peers will laugh at you and talk trash behind your back!

 
Making efficient check boxes

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:
 
  1. Assuming you're using Flex Builder, select File > New MXML Component. You might want to create a "components" folder in your Flex project and then put the code in that folder. Name it something creative. I called mine iconCheckbox.mxml and put it in a folder called mnrComponents.
  2. In your main source (main.mxml), you'll need to connect the two files. To do this, add a 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…
 

 
Playing pass the icon

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.
 
 
Embed the icons
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.
 
 
Pass the icons
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>
 
Add a few other details
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.
 
 
Try it!
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.
 
 
Manipulating objects outside of the component
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.
 
 
Back to main.mxml
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}" />

 
Where to go from here

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.