How to change the UI of a Data Grid at runtime

Some times you want to change the appearance of a data grid column or a cell at runtime. Maybe you want to react to a mouse click on a row/cell and you want to change the model behind as well as the UI presentation.

The single way to change the display of a list control (data grid, list, menu, and tree are all list controls) in Flex is to use item renderers. Each list component from Flex has a default renderer. For example, data grid has a default renderer called DataGridItemRenderer, which assumes that the data you want to display are text. When you want to display the data as something other than text, you need to create your own item renderer and then set the data grid to use your item renderer.

Item renderers act as a view for your data. And because they are not actually holding your data if your data grid displays 50 elements and you can see only 10 in the same time, it will create about 10 and reuse them as you scroll up and down. Reusing the item renderers is more efficient than creating all the item renderers for rows that are not in the current view.

The simplest way to create a custom item renderer for your data grid is inline. Using the tag mx:itemRenderer and than mx:Component you can declare any components from Flex you want. In order to get the data for the current row you use the “data” property. In the following example I create a custom renderer out of a VBox that hosts two other components: a label and a image. And to retrieve the “src” property for the current row I use this syntax: “data.src”.

<mx:DataGrid dataProvider="{myData.item}">
    <mx:columns>
        <mx:DataGridColumn>
            <mx:itemRenderer>
                <mx:Component>
                    <mx:VBox>
                        <mx:Label text="{data.src}" />                        <mx:Image source="{data.src}"/>
                    </mx:VBox>
                </mx:Component>
            </mx:itemRenderer>
        </mx:DataGridColumn>
    </mx:columns>
</mx:DataGrid>

<mx:Model id="myData">
    <root>
        <item>
            <src>pic1.jpg</src>
        </item>
        <item>
            <src>pic2.jpg</src>
        </item>
    </root>
</mx:Model>

In order to use a component as an item renderer and to be able to bind the “data” property to it, the component must implement this interface: mx.core.IDataRenderer. Most Flex components implement this interface and this is why my previous example worked. However, if you create your own UIComponent and you are not extending a Flex component you will need to implement the interface IDataRenderer.

Now, depending on your use case, you might want to:

  1. change the appearance of an entire column from your data grid
  2. change just one cell from your data grid

As usual I created a sample project. At the end of this article you can find a link for testing the code (you can right click on the page and choose view source from the contextual menu) and one for downloading the project.

Changing the appearance of an entire column

This is the easie of the two task. Suppose I want to change the appearance of the whole column when some one clicks on the data grid. First I will create two item renderers, one for each view of the column. Then I assign to the data grid the default item renderer for the column I want to customize. Next I attach an click item event listener on the data grid and inside of this event listener I switch the default item renderer with the second one. For fun I use a flag, to toggle the views. The interesting code is the event listener function. You don’t set directly the new item renderer to the column itemRenderer property. Instead, you set the item renderer that you want to be used wrapped in a ClassFactory object. This object it is used to generate instances of the given type with identical properties.

/**
 * @var flag to keep track of what item renderer to use for the first data grid
 */
private var toggle:Boolean = false;

/**
 * Event listener registered for the first data grid for Item click events.
 * It changes the item renderer for the second column of the data grid
 * @param event
 */
private function clickCell(event:Event):void {
    if (!(event is ListEvent))
        return;
    //retrieve the data grid object    
    var grid:DataGrid = (event as ListEvent).target as DataGrid;
    //using the columns array from the data grid, we change the item renderer for the second column
    //by assigning a different item render for the "itemRenderer" property of the column
    if (!toggle)
        (grid.columns[1] as DataGridColumn).itemRenderer = new ClassFactory(ItemRenderer1);
    else
        (grid.columns[1] as DataGridColumn).itemRenderer = new ClassFactory(ItemRenderer2);
    toggle = !toggle;
}

Changing the appearance of one cell

If you want to change just one cell, you can’t switch the renderers as this action will change the whole icon. Instead you can create your custom item renderer with different states, one for each different view you want. The default state will be the one you want to be displayed by the data grid when the data is loaded. To change the state for a given item renderer, you change the data displayed by the row. And in your renderer you evaluate the data to decide what view to display. Here is the code for item renderer:

<?xml version="1.0" encoding="utf-8"?>
<mx:VBox xmlns:mx="http://www.adobe.com/2006/mxml" horizontalAlign="center">
<mx:Script>
    <![CDATA[
        import mx.events.FlexEvent;

        /**
         * over ride the set data function in order 
         * to control the states we want to display
         */
        override public function set data(value:Object):void {
            if (value != null) {
                super.data = value;
                if (value.showProgresBar) {
                    currentState = "loading";
                } else {
                    currentState = "";
                }
                // Dispatch the dataChange event.
                dispatchEvent(new FlexEvent(FlexEvent.DATA_CHANGE));
            }
        }
    ]]>
</mx:Script>
    <mx:states>
        <mx:State name="loading">
            <mx:AddChild position="lastChild">
                <mx:ProgressBar indeterminate="true"/>
            </mx:AddChild>
        </mx:State>
    </mx:states>
    <mx:Label text="{data.price}" />
</mx:VBox>

Remember when I said that most of the Flex components implements IDataRenderer? By over riding the setter method, I am able to control what state I want to display.

The code that modifies the data model is inside of the event listener registered for the data grid item click event. It retrieves the data object from the item renderer, and because this object is of type Object, I can set dynamically a property. In this case, I add “showProgresBar” and I set to true. As the data provider is bindable and I just change one item, the set data method is called on my item renderer for each row from the current view. This gives me the opportunity to check the presence and the value of this flag and to switch the state.

/**
 * The listener for click item event for the second data grid.
 * It changes the appearance of the cell, by changing the data model, 
 * which in turns it is used by the custom item renderer to decide 
 * what state to display. 
 * @param event
 */
private function clickCell2(event:Event):void {
    if (!(event is ListEvent))
        return;
    var ev:ListEvent = event as ListEvent;
    //retrieve the data object associated with the current row
    var data:Object = ev.itemRenderer.data;
    if (data["showProgresBar"] == null || data["showProgresBar"] == false) {
        //set the showProgresBar to true to change the UI of the cell 
        data.showProgresBar = true;
    } else {
        //set the showProgresBar to false to change the UI of the cell 
        data.showProgresBar = false;
    }
}

The wrong way

You may be tempted to try another approach to change the appearance of a cell: to retrieve the item renderer of the cell you clicked on and directly inject into it new components. This is the wrong way to do it. For a instructional purposes, I included in an example along these lines in my project, and you can test it. In some cases will its seems to work, but remember that the data grid reuses the item renderers when you scroll down. So if you click on the first row, a progress bar is inserted in the second cell from the row, and as you start scrolling down, you will see other cells with the same progress bar (this is because of renderer reuse).

You can test this code here and you can download the project from here (choose Import > Flex Builder > Flex Project wizard to import it into Flex Builder).

6 thoughts on “How to change the UI of a Data Grid at runtime

  1. thanks for the hint.

    by the way, i’ve adapted this to a TreeItemRenderer, and i had to add the validateNow() method just after setting the new renderer.
    if i don’t do that, sometimes an error 1010 show up on List.as when i open node and close it before changing the renderer.

    tree.itemRenderer = new ClassFactory(MyTreeItemRenderer);
    tree.validateNow();

Leave a Reply

Your email address will not be published. Required fields are marked *