June 2, 2003

component tutorial #1 - Inside Flistbox

Since components have become such a central part of Flash development in the short time that they have been in existence, I figured I'd take a shot at writing down some of my insights into the FUIComponents that I have had experience using over the past several months. I still have a lot to learn, but maybe some people can benefit from the knowledge I have acquired in the area of FUIComponent architecture (not to say that I'm the expert here...see Nigel Pegg). If you really want some good insight on the background behind FUIComponents, read Nigel's chapters in Object-Oriented Programming with ActionScript.

So over the course of the next while, I hope to focus on several of the FUIComponents and break down some of what I feel are the most important parts of these beasts. Why bother, you ask? Well, I would say for at least 3 reasons:

  1. The FUIComponent set is probably THE most used set of components around. They comprise some of the most commonly used UI elements that designers use today. Most projects will require the functionality available in at least one of these.
  2. The FUIComponent set is built around a common framework, with several elements of reuse between components. This means that if you can understand some of the fundamentals of one component, you have a really good start at understanding the others.
  3. The FUIComponent set was built with the very difficult task of trying to "fit" as many potential implementation situations as possible. Due to that fact, certain functionality may have been left out, and at some point, you may want to extend a component to add functionality to it. Understanding something about the architecture of the component will save you hours of time and aggrivation and make for an all-around fun experience!

Having said that, I'll start with the component I have become most familiar with over the recent weeks...FListBox! Ah, the exhilaration you feel when you run "addItem()" on your listbox and items magically appear with working scrollbar, selection handling, data storage, focus management and the like. Overall, it has a LOT of functionality and is well designed. But what is really going on under the hood? The nice thing is that you don't really need to know in order to use it. However, if you yearn to understand more, keep reading...

If you are not already somewhat familiar with FListBox (ie., what it is capable of doing), I would recommend looking through the Flash MX reference panel for FListBox (under the Flash UI components category). This lists and explains all of the PUBLIC methods that are provided to manipulate FListBox.

I have used gModeler (by my good buddy Grant Skinner :) to draw a "pseudo class diagram" of FListBoxClass. The reason I say "pseudo" is because I really don't have formal training with UML (mostly bits and pieces I've picked up), and the purpose of doing this was not to create a perfect class diagram, but more to provide a visual layout of the component and all of it's "innards". I've also included some notes related to some of the classes. I have NOT included descriptive documentation regarding all of the individual methods/events/properties, however, if you feel like doing so, please let me know!

View a screenshot of the diagram.
View the gModeler xml of the diagram (to use this, just copy the xml source from this link and paste it into gModeler).

In case you weren't already aware, even though FListBox is the actual component, and sits in the Flash UI Components folder in your library, a lot of the classes I am going to be referring to are located in:

Flash UI Components > Core Assets > FUIComponent Class Tree

Make sure you take a peek through there because we are going to get fairly intimate with the contents of that folder.

The foundation of FUIComponentClass

Flash UI Components > Core Assets > FUIComponent Class Tree > FUIComponent

Before you begin to focus on any one FUIComponent, you should know that the foundation that these components are built upon is the FUIComponentClass. So what does this mysterious base class do? Well, here is a list of basic functionality encapsulated within FUIComponentClass:

  1. Accessibility management
  2. Focus management
  3. Enabled state management - ability to set a component as "enabled" or "disabled".
  4. Implementation of selection change handler - ability to set a change handler for a component as well as execute that change handler upon selection change.
  5. Implementation of style properties - can be accessed via "setStyleProperty" run on the component itself, or FStyleFormat/globalStyleFormat with the component listening for style updates.
  6. Implemenation of functionality to allow skin elements to be registered to a component and thus be manipulated via style properties (see above).
  7. Invalidation - preventing the same method call from occuring multiple times within the same code execution block, which can occur when setting multiple component values at the same time. A couple of great articles by James Smith on the invalidate method and how FUIComponents implement it.

Whew! That's a lot of stuff for a class that doesn't get much time in the limelight. Obviously, since this is a base class, some of these methods do get overridden by it's subclasses, but there is a lot of functionality there if you need it. So if you're ever thinking about creating your own component by extending FUIComponentClass, ask yourself if you need any or all of these features...it may help in your decision.

Another good article by James Smith on A close look at FUIComponentClass.

If you want more specific details (I don't have room to cover them here), have a peek at FUIComponentClass Actionscript Dictionary written by Peter Hall. It lists and gives descriptions of, all methods, events and important internal properties of FUIComponentClass. Also, Jesse Warden has converted these documents into FUIComponentClass reference docs that can be installed into the Flash MX reference panel. Thanks guys!

Total compressed size of FUIComponentClass: 2.4k

The data storage of DataProviderClass

Flash UI Components > Core Assets > FUIComponent Class Tree > DataProvider

This is another class that is generally hidden away from the developer (or at least it was until people dug in and found it...now it's commonly used). This class is used by default to handle/store all data that is being used by so-called "data aware" components. These are components that generally require handling of multiple data items...examples are FListBox, FComboBox, FTree, FBarChart, FLineChart, FPieChart and others. Components that are NOT data aware include FPushButton, FRadioButton, FCheckBox, FScrollBar and FScrollPane. A quick way of seeing if a component is a so-called "data aware" component is to start with a blank Flash movie and drag the component onto the stage. Then check your library and, if you look in the path mentioned above for DataProvider and it's there, then it is likely data aware.

The idea behind the dataprovider is that it is often best to keep your data and your UI as seperate entities in your project. This way, if your UI needs to change, you don't have to change your data in any way...you just build your new UI to fit into the existing data. This is often referred to as the MVC (model-view-controller) pattern, and there has been a lot of discussion related to it in the Flash community lately. Though there are arguments as to whether the FUIComponents truly fit into this pattern, the idea behind dataprovider grew out of it. For more details on this pattern as it relates to Actionscript specifically, read Object-Oriented Programming with ActionScript by Branden Hall and Samuel Wan.

Essentially, DataProviderClass is an API giving you access to store and manipulate data "objects" as name-value (label-data) pairs, as well as provide a way of having the data be displayed by multiple views (UI elements). DataProviderClass contains methods allowing you to:

  1. Add/remove/sort/count data items(addItem, addItemAt, removeItemAt, removeAll, replaceItemAt, sortItemsBy, getLength).
  2. Get individual item information (getItemAt, getItemID) .
  3. Add UI elements to "listen" for changes in the item information and update their displays accordingly (addView).
  4. Broadcast changes in the dataprovider to the associated views (updateViews).

Here is a simple order of execution when something changes in the dataprovider (adding an item in this case):

  1. "addItem" is called on the dataprovider instance to add a new item to the "items" array (the array holding individual item data).
  2. The item is added to the array (the end of the array in the case of addItem).
  3. The dataprovider instance calls "updateViews" on itself, passing information about the type of change that occured in the dataprovider ("addRows").
  4. The dataprovider loops through all of the views in it's "views" array (the array holding a list of references to UI elements associated with the dataprovider) and calls "modelChanged" on all of them (notifying them that the "model has changed"). The "modelChanged" method will vary from UI element to UI element. This is the beauty of this system...different UI elements can update themselves in different ways (through different "modelChanged" code), but the data shared by those elements always stays the same.

Branden Hall has written a good introduction to DataProviderClass (look under the "data du jour" heading) for Macromedia.

Also, Jesse Warden has written and made available Flash MX reference docs for DataProviderClass.

A neat little xml to dataprovider converter was created by Greg Burch which basically allows you to transform xml directly into a dataprovider.

NOTE: If you plan on using DataProviderClass in a custom component you're building, make sure you either include an instance of it inside your component or check "Export in First Frame" ON in the Linkage Properties dialog box, since it is checked OFF by default. If you're curious, the reason why "Export in First Frame" is checked OFF is because an instance of it is included in FSelectableList (Virtual Assets layer). Thanks to Jesse for pointing that out to me.

Total compressed size of DataProviderClass: 0.9k

Onto the main event - FListBoxClass!

Now, having learned a little about two of the classes that FListBoxClass depends upon, we can dig deeper into FListBoxClass itself. Rather than try to explain what every line of code inside this component is doing (along with all of its related classes), I'll try to touch upon some of the understanding I've gained about how the component works and things you might find important to know as a developer digging deeper into these components. Here goes:

1) FListBox is not just a scrollbar/scrollpane that scrolls a bunch of movieclips up and down.

When first working with the FListBox component, before having looked at any of the innards of it, my assumption was that the FListBox was essentially a scrollpane with some list items in the scrolling movieclip. I quickly realized that this is not the case. FListBox actually will only attach 1 movieclip (FListItemSymbol) for every ROW that is VISIBLE in the listbox at any given point in time. From there, what happens is that as the user scrolls through the list, these movieclips will not physically move at all...instead, the label associated with each movieclip changes to give the appearance that list items are moving up and down. That's why, if you look closely, you never actually see list items slowly move up and down...the labels actually "jump" from one movie clip to the next to give the appearance that they are moving through the list. FScrollSelectListClass handles all of this scrolling.

Here is a brief explanation of how this occurs:

  1. In the "initScrollBar" method of FScrollSelectListClass, you'll see that the scrollbar used in FListBox is attached and given an instance name "scrollbar_mc". Now that the scrollbar is attached, there needs to be some way of capturing the "scroll" event and passing it to the listbox. This occurs a couple of lines below the scrollbar is attached, when the changeHandler for the scrollbar is set [this.scrollBar_mc.setChangeHandler("scrollHandler", this);].
  2. If you look just below the "initScrollBar" method, there is a method called "scrollHandler". This is the method referred to in the line of code above and it will execute every time the scrollbar is moved.
  3. Within the "scrollHandler" method, things like the current top item index displayed are set and the current and last scrollposition of the scrollbar is retrived and stored. However, the important part here is that FScrollSelectListClass calls "updateControl" on itself. Now, you're not going to find "updateControl" in the code for FScrollSelectListClass. That's because it's actually a method of FSelectableListClass, which is the superclass of FScrollSelectListClass, meaning that FScrollSelectListClass inherits all of the methods of FSelectableListClass. So, chug on over to FSelectableListClass...
  4. Track down the "updateControl" method of FSelectableListClass. You'll notice that there are only a couple of lines of code there. What's happening is that it's looping through (one time for every VISIBLE item in the listbox, as stored in the property "numDisplayed"), and calling "drawItem" on each of the "item" movieclips (FListItemSymbol) that were attached to the listbox. So, in your library, go find FListItemClass (called FListBoxItem in your library) so we can see what "drawItem" does...
  5. You probably noticed at this point that there are NO methods defined in FListItemClass. That's because FListItemClass is actually just "stub code for extension purposes" as mentioned in the comments for that class. That's ok because from our class diagram and from the line of code [FListItemClass.prototype = new FSelectableItemClass();] we already know that FListItemClass inherits from FSelectableItemClass...so go find that now...
  6. Finally, we've found our "drawItem" method. However, from there, it calls "displayContent" on itself to actually do the display updates. So once we find that method, we see a little "if...else" magic happening to make sure that we have a value to display as our label, and once that has occured, we finally set the label at the line [this.fLabel_mc.setLabel(tmpLabel);]. Whew!

One other thing that might be useful in a listbox is the ability to hide the scrollbar when there aren't enough items to make the listbox scroll. By default, when there aren't enough items to make the listbox scroll, the scrollbar is just disabled. You can hide the scrollbar in these instances by calling "FListBox.setAutoHideScrollBar(true);". Internally, there is a property called "permaScrollbar" which determines whether the scrollbar should display or not in these cases.

So you can see that a lot of work goes on behind the scenes to make the listbox do what it does in terms of scrolling. Now, after all of this, you may be wondering, "Why didn't they just attach a bunch of items to a scrollPane and have them scroll up and down?". Well, after thinking about this for a while, I can think of at least a three reasons:

  1. If you have even a couple of hundred items in your listbox, there are potential performance issues related to having a movie clip for every list item. First, the initial time to attach all of the movie clips would cause a lag, and could potentially cause your app to be unresponsive for a second or two.
  2. Second, if you want to add or remove items from the end of your list, that's no problem...but what happens if you want to do so somewhere in the middle of your list? Well, if you have 200 items and you want to add an item at position 3, you could either remove all 200 movie clips and reattach all of them, which would again cause a lag, or you could just attach the added item and move 197 movieclips down (ie., adjust their _y property). Again, to do this for 197 movieclips would become processor intensive to say the least! Furthermore, if you did it this second way, you'd have all sorts of issues with list item order management.
  3. Any "scrollpane-like" UI element by default, requires a mask to be placed over the pane area. The immediate disadvantage to this is that in earlier versions of the Flash player (earlier than version 6.0r47) you cannot mask device fonts, so you would not be able to display crisp text inside the list item labels (unless you use pixel fonts which of course result in increased file size because you have to embed the font).

So after finding out how it works, and trying to come up with some logical reasons why it works this way, we can see some definite advantages to this implementation. However, that's not to say there are disadvantages to doing it this way. One disadvantage that people have come across on numerous occasions is that fact that you can't really make the existing listbox contain items that have a different number of lines. For example, if you want the text in your listitem to wrap, that's not really possible using the existing listbox because each item in the listbox has to be the same height in its current implementation (as far as I know anyway).

However, you can extend FSelectableItemClass to allow you the ability to customize list items to a certain extent. Hear from the Jedi master himself on Customizing the ListBox Component. Also, Branden has written a good tute on Hacking a Grid View List Component.

2) Exactly how tall is a listbox item anyway?

You might have noticed that when you drop a listbox onto the stage, and then resize it, the listbox is not always vertically the same size as the "resize box" you use to resize it. In the following example, I've set the height of the listbox to 175 pixels, but you can see that it's obviously not 175 pixels tall:

Here is an explanation of the reason why:

One of the requirements for having the listbox behave as was explained in point #1 above is to ONLY display list items as WHOLE items (which is also the default behavior of an HTML listbox). In order to do this, obviously, a listbox's height must be an EXACT MULTIPLE of the height of the listitems within it. In order to make this work, you need to do a few things:

  1. Keep track of the authored (the listbox dragged onto the stage) or dynamically set (using setSize) height of the listbox. This is used as a guide as the MAX height that the listbox can be.
  2. Find the max height of a list item.
  3. Find the max number of items that can fit within the height of the listbox.
  4. Multiply that number by the item height (give or take a few pixels for buffer between items).
  5. Voila! You have the new height of your listbox.

If you take a look in the "setSize" method of the FListBoxClass, the code exists to do all of these things.

  1. First, before doing any of this stuff, run "Math.max(h,40);" which sets the minimum possible height of the listbox as 40 pixels.
  2. Run "measureItmHgt" on itself (located in FSelectableListClass). This is a neat little function that finds the height value of a list item by attaching a "dummy" instance of FListBoxSymbol (or whatever "this.itemSymbol" is set to), placing the string "Sizer: PjtTopg" into the FLabel instance of that symbol, and storing the height of the instance as "this.itmHgt", then removing the dummy instance. I'm not exactly sure as to why they have used the specific characters in that string (other than including characters with ascenders and descenders).
  3. Find the number of list items that should be displayed in the listbox by taking the authored listbox height and dividing it by this newfound item height (minus 2, i assume to set the items a little closer together) and Math.floor-ing it. This is stored as the property "numDisplayed" (which can be changed indirectly by calling "setRowCount").
  4. Find the new listbox height by multiplying the number of items displayed by the item height (minus 2), and then add 2.

Every time you call setSize (either by calling it directly, or by changing the row count using "setRowCount") it will repeat this set of steps to find the new size. And that's everything you need to know about vertically sizing a listbox.

3) When you manipulate items in a listbox, what you are actually doing is manipulating the items in the dataprovider

Remember all that stuff I mentioned before about dataproviders feeding data into UI elements and notifying those elements when changes occur to the data? Well, the reason why this is immediately important when working with FListBox (and most other data aware components) is that when you add/remove/sort/count items, you are not really doing anything directly to the listbox. You ARE calling methods directly on FListBox like:

  • addItem
  • addItemAt
  • replaceItemAt
  • removeItemAt
  • removeAll
  • sortItemsBy
  • getLength

In large part, what these methods do is pass that information to their associated dataprovider and the dataprovider stores and manipulates the actual data. Once it has done that, it sends out a notification to all listening "views" to update themselves (via "modelChanged" - see below).

You'll find most of the above methods in FSelectableListClass. If you take a look at these methods in this class, you'll see lots of reference to "this.dataprovider". That property contains a reference to the DataProviderClass instance that is created by the FListBox. You probably also notice that the dataprovider instance has methods with the same names as that of FSelectableListClass. These methods are called to manipulate the actual data in the listbox. To see exactly where this dataprovider instance is created, look in the "init" method of FSelectableListClass. You can see that when the listbox is created, it automatically creates a default DataProviderClass instance AND adds the listbox as a view (by running "addView"). Cool!

Now if you look a little further down in the code for FSelectableListClass, you notice a method called "setDataProvider". Does this mean we can actually set our own custom dataprovider to the listbox? Absolutely! Why bother? Well, because in the MVC pattern, you want to keep the model and view separate and you might want direct access to the model. If this is the case, you are going to want to create your own custom dataprovider instance and use that with your listbox.

4) Keeping track of selection information

Now that you understand where a listbox keeps it's data and how it scrolls through the data items, you might be wondering, "How then does the listbox handle and store selection information?" (ie., what item(s) are currently selected). Well, the listbox has a property called "selected" which is an array that stores a list of individual item ID's for every item that is selected.

One thing I didn't mention about dataprovider items is that when an item is added to the dataprovider, it is automatically assigned an id value which is stored in the property "this.__ID__" (2 underscores on either side of ID - See DataProviderClass.addItem for more details). This value starts at 0 and just increments up every time you add a new item. This is the id value that is stored in the "selected" array mentioned above.

Anyway, back onto our selected items...all item selections are set through a method called "selectItem" in FSelectableListClass. There are basically 2 ways to update selection information:

  1. Click on a list item which creates a chain reaction (FSelectableListClass.clickHandler > FListBoxClass.selectionHandler > FSelectableListClass.selectItem) that ultimately leads to the "selectItem" method being called. Note that FListBoxClass.selectionHandler contains the code to allow multiple selections (by pressing Shift or Ctrl).
  2. Run "FListBox.setSelectedIndex" which manually allows you to set the selection index and ultimately calls "selectItem"

If you take a look at the "selectItem" method, you can see that it accepts 2 parameters - index and selectedFlag:

  1. index - This value represents the item number in the list that is selected (NOT the visible item number). For example if your listbox contains 20 items, but only has 5 visible rows and you select item number 13 which is the first item showing in the listbox, the index value would be 13, not 1.
  2. selectedFlag - This value represents whether an item is being selected (true) or deselected (false).

If selectFlag is true, then an item is added to the array at the index of whatever the unique item ID (__ID__) value is of the selected item and given a value of type Object which contains a property "sIndex" which is set to the "index" parameter value. If selectFlag is false, the item in the array at the index value of the unique item id is deleted. So now that we know what happens when an item is selected, how do we get the selected index information?

Well, we can either get a single selected index value returned as a number (getSelectedIndex) or multiple selection values returned as an array (getSelectedIndices). In either case, essentially, what happens is that you loop through all of the unique ID index values in the array and grab the values of the "sIndex" property.

5) DataProvider talking to FListBox - "Your data is served"

A couple of times, I've made mention of "modelChanged". This is an important method that exists somewhere within the inheritance chain of ALL data aware components. As mentioned before, this is a way to notify the "view" (listbox) that changes in the "model" (dataprovider) have occured and that the view needs to "do something" with the updates. DataProviderClass calls this method after any add/remove/replace/sort/addView call and does so through it's own "updateViews" method, which basically loops through it's "views" array and calls modelChanged on each view.

In FListBox, the modelChanged method exists in FSelectableListClass and is overridden in FScrollSelectListClass. The modelChanged method accepts 1 parameter of type Object with the following possible properties (only the "event" property seems to be required):

  1. event - the type of event that has just occured to the dataprovider. Based on this value, the modelChanged method of FListBox will run an "if...else" condition and execute the appropriate code to update itself.
  2. firstRow - Indicates the first index to update.
  3. lastRow -Indicates the last index to update.

Generally, firstRow and lastRow have the same value unless you are removing ALL list items. A really good overview of these properties and the values set to them by different dataprovider methods is provided in the chart below (created by Jesse Warden as part of his DataProviderClass reference docs).

=========================================== METHOD PROPERTY VALUE -------------------------------------------- addView event updateAll addItemAt event addRows - firstRow index - lastRow index
addItem event addRows - firstRow index - lastRow index
removeItemAt event deleteRows - firstRow index - lastRow index
removeAll event deleteRows - firstRow 0 - lastRow length - 1
replaceItemAt event updateRows - firstRow index - lastRow index
sortItemsBy event sort
sort event sort

In FScrollSelectListClass, modelChanged performs the following:

  1. Call "super.modelChanged" (ie., the modelChanged method existing in FSelectableListClass)
  2. Once that's complete, call "iniScrollBar" on itself (to update the scrollbar).

In FSelectableListClass, modelChanged does the bulk of the update work:

  1. Check the type of change that occured to the dataprovider and execute appropriate code to update selection information. Notice that this method does NOT actually directly call "drawItem" on the listitems to redraw themselves. It does so indirectly by either calling "setSelectedIndex" (which calls updateControl through invalidation) or by calling updateControl directly.
    1. addRows - update the selected indices if they are greater than or equal to the inserted index point of the added item (selected indices before the insertion point do not need to change).
    2. deleteRows - first check to see if "firstRow == lastRow". This is only true if ONE item has been removed, and in that case, update the selected indices around the deleted item. Otherwise, all items have been removed so run "clearSelected" to remove all items and reset the selection index array.
    3. sort - resort selected indices based on comparing current selection ids with the updated dataprovider item ids.
  2. Run "updateControl" on itself through invalidation. The updateControl method is what actually causes the items to redraw themselves.

6) How FListBox listens for keys

If you've played around with a listbox, you probably noticed that FListBox responds to key press events. More specifically, it can handle:

  1. Up & Down arrows, Page Up, Page Down, Home and End to move your selection up or down in the listbox.
  2. Character keys (between ascii codes 33 and 126) to move your selection to a list item that has a label starting with the character you just typed (this only works for single characters, not multiple as far as I can tell). If you keep hitting the same character, it starts at the first item in the list starting with that character, then moves sequentially through all of the items in your list that start with that character.

When you create an instance of FListBox, the FUIComponentClass "init" method automatically sets up an object to listen (aptly named "keyListener") for key events. Then, when focus is given to the listbox (and "myOnSetFocus" is called), that's when keyListener is added to the Key listener array (when focus is removed, keyListener is removed as a listener).

When a key is pressed, it broadcasts the event to the "myOnKeyDown" method of FScrollSelectListClass. If a key from category "a" (see above) is pressed, this method calls "moveSelBy" on itself to move the selected index appropriately. If a key from category "b" is pressed, it calls "findInputText" on itself, which in turn calls "findString". This is the method that compares the character that has been pressed with the first character of the list item label to see if a match occurs, and calls "moveSelBy" to move the selected index to that item (if it exists).

7) A few little "bugs" in FListBox

If you're using FListBox, there are a few little "bugs" in the component that you should be aware of. I have made note of 3 of them, and posted fixes for them (not all my fixes). These are by no means Macromedia sanctioned fixes, and could possibly cause other things to break. I guess only time and testing will tell.

I have made mention of these in a previous post, so take a look here:

A few issues with FListBox

Total compressed size of FListBoxClass: 13.3k (includes ALL subclasses including DataProviderClass and FUIComponentClass)

Conclusion

Well, that's probably about all you'll ever care to know about FListBox. Of course, Nigel has mentioned recently that he's working on an updated version of the listbox, so who knows how much of this will still be applicable when that happens. Maybe he can give us an idea :)

Finally, here is the info for the gModeler diagram once again:

View a screenshot of the diagram.
View the gModeler xml of the diagram (to use this, just copy the xml source from this link and paste it into gModeler).

Special thanks to Jesse for proof reading and Grant for advice on the gModeler class diagram.