A description of the List component

The FlexJS List component is fairly complex component compared to such components as Label, TextButton, and even Panel. Originally, the List component started out simply, especially on the JavaScript side, but it is now a more robust component.

The original List component (now SimpleList) consisted of a model and item renderers but on the JavaScript side, it was merely a wrapper for the HTML <select> element. Since the JavaScript FlexJS components should reflect their ActionScript counterparts as much as possible, the JavaScript List had to also make use of item renderers.This is that story.

The following diagram shows how the ActionScript List component is composed.

ListComponent

The List contains:

  • A selection model which encapsulates a data provider as well knowledge about which item is selected (the item and the index).
  • A view bead that displays the container housing the list control.
  • A factory bead that can produce instances of item renderers as needed.
  • An item renderer to display each element of the list; created by the factory bead.
  • A mouse controller for the list as a whole that handles the selection for the list.
  • A mouse controller for item renderer to handle the hover and selected states.

These parts are specified by the List's style definition in the defaults.css file:

List
{
    IBeadModel: ClassReference("org.apache.flex.html.beads.models.ArraySelectionModel");
    IBeadView:  ClassReference("org.apache.flex.html.beads.ListView");       
    IBeadController: ClassReference("org.apache.flex.html.beads.controllers.ListSingleSelectionMouseController");
    IBeadLayout: ClassReference("org.apache.flex.html.beads.layouts.VerticalScrollingLayout");
    IDataGroup: ClassReference("org.apache.flex.html.supportClasses.DataGroup");
    IDataProviderItemRendererMapper: ClassReference("org.apache.flex.html.beads.DataItemRendererFactoryForArrayData");
    IItemRendererClassFactory: ClassReference("org.apache.flex.core.ItemRendererClassFactory");
    IItemRenderer: ClassReference("org.apache.flex.html.supportClasses.StringItemRenderer");
}

The diagram above does not show the item renderer class factory nor does it specify the item renderer. For the List, the IDataProviderItemRendererMapper marries the information. That is, when the List component is being created, an instance of the IDataProviderItemRendererMapper is created and it looks at the List's style definition for the IItemRendererClassFactory which it instantiates. Then for each item in the data provider, the IDataProviderItemRendererMapper has the IItemRendererClassFactory create a new instance of the IItemRenderer.

Item Renderers

Once the List component and all of the item renderers have been created, the interactive part comes into play. Each item renderer is also a component (strand and beads) and follows the same pattern as other FlexJS components. For the List, the IItemRenderer is specified in defaults.css as the StringItemRenderer class which has its own style definition, show here:

StringItemRenderer
{
    IBeadController: ClassReference("org.apache.flex.html.beads.controllers.ItemRendererMouseController");
    height: 16;
}

The StringItemRenderer's IBeadController is the generic ItemRendererMouseController which looks for mouse events: over, out, down, and up. These events set the item renderer's hover and selected properties.

When the item renderer's controller selects an item renderer, it also dispatches an event that is picked up the List's controller, ListSingleSelectionMouseController. This controller updates the List's model and dispatch's the List's change event.

JavaScript

The JavaScript implementation of List closely follows the ActionScript implementation, although they are not exact. The main difference is that events in ActionScript are chained through the component's display list parts (the item renderers and the view bead which have a parent/child relationship on the display list). That is, the bead classes that make up the view and item renderers extend Flash display list objects.

In the JavaScript, the bead classes are normal classes and events do not propagate from inner to outer class (the UI in JavaScript are HTML elements that the JavaScript classes are wrapping). This event flow makes it a little challenging to have an event dispatched on an item renderer be detected by the ListSingleSelectionMouseController which is listening for events coming through the view bead. In JavaScript, the event dispatched on the item renderer does not have an event chain parent so that detection is not possible.

// from ItemRendererMouseController's handleMouseUp function
this.strand_.get_itemRendererParent().dispatchEvent(newEvent);

Instead, the JavaScript implementation of the List component provides a property, itemRendererParent, to help with the event flow. By dispatching on this property, the same event can reach the ListSingleSelectionMouseController so that the model can be updated in the same way it does in the ActionScript version.

Custom ItemRenderer

This section shows you how to make a custom itemRenderer for a List control.

The first thing to do is map out your data: what does your data look like? For this example, the data is a list of products each having an image of the product, the product's ID, title, and stock status (in stock or out of stock).

The next thing to do is design the itemRender: what do you need to show in each itemRenderer? For this example, the itemRenderer will show an image, a title, and single detail line of text which will be the stock status.

Example ItemRenderer

FlexJS comes with two pre-built itemRenderers and there will probably be more. The TextItemRenderer is used exclusively for arrays of strings and is used with the SimpleList control. The DataItemRenderer is useful for more complex data and that is the class your itemRenderers will mostly extend.

public class ProductItemRenderer extends DataItemRenderer
{
    public function ProductItemRenderer()
    {
        super();
    }
		
    private var image:Image;
    private var title:Label;
    private var detail:Label;
}

The example, started above, shows the ProductItemRenderer extending DataItemRenderer and defining the components it needs to display the image, title, and detail elements.

Every itemRenderer should override the addedToParent() function and instantiate the components that make up the itemRenderer, as shown here:

    override public function addedToParent():void
    {
        super.addedToParent();

        image = new Image();
        addElement(image);
			
        title = new Label();
        addElement(title);
			
        detail = new Label();
        addElement(detail);
    }

The addedToParent() function is called when the itemRenderer has been instantiated and added to the display list. In the example, the image, title, and detail are created and added to the itemRenderer's display list.

Once an itemRenderer has been created, it will be given the data to show by setting the itemRenderer's data property (virtualized lists, which are not yet developed in FlexJS, will recycle the itemRenderers and set the data property whenever they can used).

    override public function set data(value:Object):void
    {
        super.data = value;
			
        image.source = data.image;
        title.text = data.title;
        detail.text = data.detail;
    }

The override of the data property setter should always call super.data = value as the first thing to ensure the data getter returns the correct thing. All this override does it set the appropriate property on the components in the itemRenderer. For example, the title component has its text property set from the data' title property.

Finally, the itemRenderer should respond to changes in its size. The base class, DataItemRenderer, intercepts the events that trigger this and automatically calls its adjustSize() function.

    override public function adjustSize():void
    {
        var cy:Number = this.height/2;
			
        image.x = 4;
        image.y = cy - 16;
        image.width = 32;
        image.height = 32;
			
        title.x = 40;
        title.y = cy - 16;
			
        detail.x = 40;
        detail.y = cy;
			
        updateRenderer();
    }

This itemRenderer sample determines the vertical center of its space and then positions the image, title, and detail components from that. Your itemRenderer can do whatever it needs to size and position its component children.

The final step adjustSize() should do is call updateRenderer() which will take care of any background color change in case the itemRenderer is selected or being hovered by the mouse.

While this example does not do it, your itemRenderer may also want to override updateRenderer() and provide different experiences for the hovered and selected states.

Making it Work

Once you have created your itemRenderer you can add it a List control via a style definition (this one appears in the style block of MyInitialView):

<fx:Style>
    @namespace basic "library://ns.apache.org/flexjs/basic";
    @namespace sample "products.*";

    sample|ProductItemRenderer {
        height: 40;
        IBeadController: ClassReference("org.apache.flex.html.beads.controllers.ItemRendererMouseController");
    }
</fx:Style>

<basic:List id="productList"  x="20" y="400" width="200" height="150" className="productList">
    <basic:beads>
        <basic:ConstantBinding
                 sourceID="applicationModel"
                 sourcePropertyName="products"
                 destinationPropertyName="dataProvider" />
    </basic:beads>
</basic:List>

The itemRenderer, ProductItemRenderer, is actually in the products package so a @namespace is used to identify it in the style definition. The style definition sets the ProductItemRenderer's height to 40 pixels and makes it use the ItemRendererMouseController to handle the mouse events for roll over, roll out, up, and down which translate to the itemRenderer's hover and selected states.

JavaScript

The example is for ActionScript only. Custom JavaScript itemRenderers are a work in progress.

  • No labels