Effortless Flex 4 Development: Item Renderers
Item Renderers
Many of the data-driven components, including List, ComboBox, DropDownList, Tree, DataGrid, and AdvancedDataGrid, can use item renderers to control how data is displayed. Whereas a labelFunction can customize the displayed information, an item renderer acts more like a template, and allows you to display more than just a string.
Item renderers can be written in ActionScript or MXML, but I'll use MXML here to make the discussion most approachable. For the example, I'm going to (egotistically) list a few of the books I've written. To start, I've got the following ArrayList of information about the books:
private var myBooks:ArrayList = new ArrayList([ {title: 'PHP 6 and MySQL 5 for Dynamic Web Sites', year: 2008, image: '../assets/php6mysql5.png'}, {title: 'Adobe AIR with Ajax', year: 2008, image: '../assets/air.png'}, {title: 'Ruby', year: 2009, image: '../assets/ruby.png'} ]);
I can easily display each title in a List like so (Figure 6.7):
<s:List dataProvider="{myBooks}" labelField="title" />
If I want to display the title and publication year, I could make use of a label function:
<s:List dataProvider="{myBooks}" labelFunction="formatListLabel" /> private function formatListLabel(item:Object):String { return item.title + ' (' + item.year + ')'; }
But if I wanted to also display the image, I can't do that using a label function, as such functions only return strings. The alternative is to use an item renderer. An item renderer allows me to specify the components and formatting to use to display an individual item. This is how I might want each item to be displayed using components:
<s:HGroup gap="5"> <mx:Image source="image" /> <s:Label text="title" fontSize="16" /> <s:Label text="(year)" fontSize="12" /> </s:HGroup>
That combination of components, wrapped within an HGroup, will give me the layout shown in Figure 6.8. Certainly you could do more to make this look fancy, but this is already so much more elaborate than what can be accomplished using a label function.
In those components, the image, title, and year values need to come from the individual item being displayed. Within item renderers, the currently rendered item will automatically be available through an object named data. So the item renderer template can actually be defined as
<s:HGroup gap="5"> <mx:Image source="{data.image}" /> <s:Label text="{data.title}" fontSize="16" /> <s:Label text="({data.year})" fontSize="12" /> </s:HGroup>
There are three places you can define this renderer so that it's usable by a component: in an external file, in the Declarations section, and inline. I'll demonstrate each separately. Note that over the next several pages, I'll create an item renderer to be used with a List component. After those demonstrations, I'll briefly discuss how item renderers to be used by other components will differ.
External Item Renderers
Here are the steps you would take to define an item renderer in an external file:
- Create a new, MXML file.
- Add the XML declaration:
<?xml version="1.0" encoding="utf-8"?>
All XML documents should begin with this. - Create an opening ItemRenderer tag:
<s:ItemRenderer xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/mx">
The ItemRenderer component is defined in Spark. In its opening tag, you should declare the namespaces, just as you do in the primary MXML file's Application tag. This is necessary so that this file can reference components such as Image and Label, defined in the Halo and Spark namespaces. - Define the item renderer:
<s:HGroup gap="5" paddingTop="15"> <mx:Image source="../assets/{data.image}" /> <s:Label text="{data.title}" fontSize="16" /> <s:Label text="({data.year})" fontSize="12" /> </s:HGroup>
This code is exactly as you've already seen it, save for the addition of the paddingTop property. As the renderer will be used by multiple, subsequent items, I want to create a gap between each, accomplished using paddingTop (you could also use paddingBottom). Conversely, the HGroup's gap property creates a five-pixel gap among the children of the HGroup: the Image, Label, and Label. - Close the ItemRenderer tag:
</s:ItemRenderer>
Save the file in your project's directory.
I would recommend using the backwards domain naming scheme, first introduced in Chapter 3, "ActionScript You Need to Know." My company's name is DMC Insights, so I would create, within the src folder, a com folder. Within that I would create a dmcinsights folder. Within that folder, I would save this file as BookListRenderer.mxml (Figure 6.9).
Alternatives would be to store the file in src/components/renderers or src/views/renderers. It's really a matter of personal preference how you organize these files; just choose a route that makes sense to you.
To use the item renderer, add itemRenderer=" path/to/renderer" to the List tag in the primary MXML file. Assuming the renderer is stored in src/com/dmcinsights/BookListRenderer.mxml, you would use
<s:List dataProvider="{myBooks}" itemRenderer="com.dmcinsights. BookListRenderer">
Note that you just use the basename of the file as the name of the renderer, without the .mxml extension. Figure 6.10 shows the same List as in Figure 6.7, now using the item renderer.
Declared Item Renderers
If you'd rather not separate out your renderer definition from the application that uses it, you can define a renderer in the primary MXML file. As the renderer is a non-visual component (on its own, that is; it's used by a visual component), you would create it within the Declarations section. To do so, you still make use of the ItemRenderer tags, but you don't need to include the namespace declarations, as the file already has those. But ItemRenderer element must be wrapped within a Component element, defined in the fx namespace. This element is used to define your own components. You use its className property to assign a unique class identifier (i.e., name) to the made-up component. Here, then, is the same renderer defined in the Declarations section:
<fx:Declarations> <fx:Component className="BookListRenderer"> <s:ItemRenderer> <s:HGroup gap="5" paddingTop="15"> <mx:Image source="{data.image}" /> <s:Label text="{data.title}" fontSize="16" /> <s:Label text="({data.year})" fontSize="12" /> </s:HGroup> </s:ItemRenderer> </fx:Component> </fx:Declarations>
You'll note that the renderer still uses the data object to reference individual values.
To use this renderer in a component, identify it with the itemRenderer property:
<s:List dataProvider="{myBooks}" itemRenderer="BookListRenderer">
In this case, as the renderer is defined within the same application file, you don't need to provide any reference to it such as com.dmcinsights.
Inline Renderers
The third way to define renderers is inline. Just as you can declare a data source within a component, you can also declare a renderer within a component. When you do so, you don't use the itemRenderer property in the opening tag. Instead, use opening and closing itemRenderer tags within the component, where the definition will take place. As with defining a renderer in the Declarations, you don't need to include the namespace declarations, but you do need to wrap the renderer within Component. Here's the same List:
<s:List dataProvider="{myBooks}"> <s:itemRenderer> <fx:Component> <s:ItemRenderer> <s:HGroup gap="5" paddingTop="15"> <mx:Image source="{data.image}" /> <s:Label text="{data.title}" fontSize="16" /> <s:Label text="({data.year})" fontSize="12" /> </s:HGroup> </s:ItemRenderer> </fx:Component> </s:itemRenderer> </s:List>
Drop-In Item Renderers
Another way you can work with item renderers is by using so-called drop-in renderers, which is to say using another Flex component as the renderer. For example, a DataGridColumn uses a Label as its renderer by default. Figure 6.11 shows how my list of books would be displayed in a DataGrid without any customization of the renderers.
Logically, instead of showing the name of the image, I would want to show the image itself. To simply accomplish this, just tell the DataGridColumn to use an Image component as its renderer (Figure 6.12):
<mx:DataGrid dataProvider="{myBooks}" rowHeight="50"> <mx:columns> <mx:DataGridColumn dataField="image" itemRenderer= "mx.controls.Image" /> <mx:DataGridColumn dataField="title" /> <mx:DataGridColumn dataField="year" /> </mx:columns> </mx:DataGrid>
Comparing Component Renderers
Except for the preceding example of a drop-in renderer used by a DataGridColumn, the item renderers created in this section were all used by a List component. In every case, the renderer was defined within s:ItemRenderer tags. When creating renderers for a ComboBox or a DropDownList, you would still want to use the ItemRenderer tags. However, the ComboBox and DropDownList components can only display a single line of text for each element, so you're pretty much restricted to using the Label and RichText components in the renderers. But you can still do more using, say, RichText in a renderer than you can just using a label function. Note the following example (Figure 6.13):
<s:DropDownList dataProvider="{myBooks}" width="400"> <s:itemRenderer> <fx:Component> <s:ItemRenderer> <s:RichText> <s:textFlow> <s:TextFlow> <s:span fontSize="16">{data.title}</s:span> <s:tab/><s:span fontStyle="italic" fontSize= "12">{data.year}</s:span> </s:TextFlow> </s:textFlow> </s:RichText> </s:ItemRenderer> </fx:Component> </s:itemRenderer> </s:DropDownList>
An item renderer to be used by a Tree is defined within MXTreeItemRenderer tags. It makes use of view states, a topic to be discussed in Chapter 11, "Improving the User Experience."
The DataGrid and AdvancedDataGrid components have MXDataGridItemRenderer and MXAdvancedDataGridItemRenderer tags for defining renderers. You can use multiple components within these renderers, including non-text ones. However, you should add top, bottom, left, and right properties to the renderer components in order to absolutely position them within each cell.