Formatting a Flex DataGrid control using a custom item renderer

The following example formats a column in a Flex DataGrid and uses a custom item renderer to color the text red in a cell if a price is below $0. If the item is greater than $0, the test is displayed in black. The price column is also formatted using a custom label function, which uses a CurrencyFormatter, and finally, the data grid column uses a custom sort function to properly sort numeric columns.

Full code after the jump.

<?xml version="1.0" encoding="utf-8"?>
<!-- http://blog.flexexamples.com/2007/08/20/formatting-a-flex-datagrid-control-using-a-custom-item-renderer/ -->
<mx:Application name="DataGridColumn_itemRenderer_test "
        xmlns:mx="http://www.adobe.com/2006/mxml"
        layout="vertical"
        verticalAlign="middle"
        backgroundColor="white">
 
    <mx:Script>
        <![CDATA[
            import mx.controls.dataGridClasses.DataGridColumn;
            import mx.utils.ObjectUtil;
 
            private function price_labelFunc(item:Object, column:DataGridColumn):String {
                return currencyFormatter.format(item.@price);
            }
 
            private function price_sortCompareFunc(itemA:Object, itemB:Object):int {
                return ObjectUtil.numericCompare(itemA.@price, itemB.@price);
            }
        ]]>
    </mx:Script>
 
    <mx:XML id="itemsXML">
        <items>
            <item name="Item 1" price="1.32" />
            <item name="Item 2" price="-12.23" />
            <item name="Item 3" price="4.96" />
            <item name="Item 4" price="-0.94" />
        </items>
    </mx:XML>
 
    <mx:Style>
        .centered {
            text-align: center;
        }
    </mx:Style>
 
    <mx:CurrencyFormatter id="currencyFormatter"
            precision="2"
            useNegativeSign="false" />
 
    <mx:DataGrid id="dataGrid" dataProvider="{itemsXML.item}">
        <mx:columns>
            <mx:DataGridColumn dataField="@name"
                    headerText="Name:"
                    headerStyleName="centered" />
 
            <mx:DataGridColumn dataField="@price"
                    headerText="Price:"
                    textAlign="right"
                    headerStyleName="centered"
                    labelFunction="price_labelFunc"
                    sortCompareFunction="price_sortCompareFunc"
                    itemRenderer="PriceLabel" />
        </mx:columns>
    </mx:DataGrid>
 
</mx:Application>

And the custom item renderer, PriceLabel.as, is as follows:

/** http://blog.flexexamples.com/2007/08/20/formatting-a-flex-datagrid-control-using-a-custom-item-renderer/ */
package {
    import mx.controls.Label;
    import mx.controls.listClasses.*;
 
    public class PriceLabel extends Label {
 
        private const POSITIVE_COLOR:uint = 0x000000; // Black
        private const NEGATIVE_COLOR:uint = 0xFF0000; // Red
 
        override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void {
            super.updateDisplayList(unscaledWidth, unscaledHeight);
 
            /* Set the font color based on the item price. */
            setStyle("color", (parseFloat(data.@price) <= 0) ? NEGATIVE_COLOR : POSITIVE_COLOR);
        }
    }
}

View source is enabled in the following example.

48 thoughts on “Formatting a Flex DataGrid control using a custom item renderer

  1. Hi Peter,

    Thanks for the example! :)

    Do you know how to create an item renderer in a datagrid column that creates different components based on the data?

    For example,

    column 1…….column 2
    ————————-
    item 1………[checkbox]
    item 2………[checkbox]
    item 3………[combobox]
    item 4………[textinput]
    item 5………[textinput]

    data:
    array = [{label:'item 1',type:'checkbox'},etc]

    When I scroll or sort the headers everything screws up.

  2. Could you say me build number of Flex complier. I got another behavior of your example. Price column and cell have the same align – “right”.
    My build number: Version 2.0.1 build 155542.
    Thanx.

  3. @Ritchie,
    I’ve centered a checkbox in a datagrid column before by adding an HBox around it and using horizontalAlign=”center”

  4. An improvement to the label function might be to change the return currencyFormatter.format(item.@price); t0 return currencyFormatter.format(item[column.dataField]);

    This should make the label function usable on any column that can be formatted as currency rather than hardcoding in the column field.

  5. Hi Peter, how can I do to change the background of entire line, based in some information in the fields? Thanks.

  6. @judah (6 months later),

    i think you need to override the set data accessor – the same instances of the renderer get reused, you’ll need to change the contents of the view hierarchy when the data changes,

  7. Flex newbie… Is there a way to make this package more generic so the colomn nmae doesn’t have to be embedded in the package? This way any column that needed this formating “itemRenderer” could use this package.

    Thanks for all of your examples!!

  8. Greg C,

    Try something like this:

    package {
        import mx.controls.Label;
        import mx.controls.dataGridClasses.DataGridListData;
        import mx.controls.listClasses.*;
     
        public class PriceLabel extends Label {
     
            private const POSITIVE_COLOR:uint = 0x000000; // Black
            private const NEGATIVE_COLOR:uint = 0xFF0000; // Red
     
            override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void {
                super.updateDisplayList(unscaledWidth, unscaledHeight);
     
                var dField:String = DataGridListData(listData).dataField;
                /* Set the font color based on the item price. */
                setStyle("color", (data[dField] &lt;= 0) ? NEGATIVE_COLOR : POSITIVE_COLOR);
            }
        }
    }

    Peter

  9. Peter,
    I am trying to apply your above example to similar code for another use of a datagrid itemRenderer. So I was wondering if you could point me in the right direction if I wanted the value of an external Control to affect the threshold point of the values that are colored.

    So if I had an hslider it could be used to dynamically change the value of the cut-off point of the colored items in that column of the datagrid.

    So basically instead of:

    setStyle("color", (data[dField] <= 0) ? NEGATIVE_COLOR : POSITIVE_COLOR);
    

    it would be:

    setStyle("color", (data[dField] <= X) ? NEGATIVE _COLOR : POSITIVE _COLOR);
    

    X being the value that would be represented by the position of the hslider.

    Does this make sense?

    Since I am new to this whole package/class thing I am not sure how to access that external value inside of the package that is being called by the itemRenderer of a grid column.

    Thanks again!

  10. Peter, Once again sorry for the double post… Any thoughts regarding how I can go about passing a dynamic value from a control into this package? I have tried a couple things and none of which seem to work.

    Thanks,
    Greg C

  11. Greg C,

    Probably not the best solution, but you could try using Application.application.slider.value. I’m not sure how well it would work since the itemRenderers wouldn’t necessarily be updated when the slider changes.

    Peter

  12. Nope, when the prices are loaded you get ($1.32, $12.23, $4.96, $0.94), if you sort the price column you get ($12.23, $0.94, $1.32, $4.96) instead of ($12.23, $4.96, $1.32, $0.94), and if you sort again to get the numbers from descendant to ascendant you have($4.96, $1.32, $0.95, $12.23) instead of ($0.95, $1.32, $4.96, $12.23)…

  13. janet,

    Interesting. I see the same default sort as you (1.32, -12.23, 4.96, -0.94) — the items are unsorted and appear in the same order they were specified in the data provider.
    If I sort by price (descending), I get the following: -12.23, -0.94, 1.32, 4.96 (or Item 2, Item 4, Item 1, Item 3). This sort is correct since the biggest negative values are first and the biggest positive values are last.
    If I sort by price (ascending), I get the following: 4.96, 1.32, -0.94, -12.23 (or Item 3, Item 1, Item 4, Item 2). This sort is correct since the biggest positive values are first and the biggest negative values are last.

    Actually, are you sure your numbers are right? 0.94 and 12.23 are both negative numbers. So I wouldn’t expect the price column to sort as 12.23, 4.96, 1.32, 0.94 since both 0.94 and 12.23 are negative numbers in the data provider. I’d expect 4.96, 1.32, -0.94, -12.23.

    Peter

  14. hi,

    i have developed a similar example. the difference is that my item renderer is a linkbutton extend class. The override function is the next:

    override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void {
        super.updateDisplayList(unscaledWidth, unscaledHeight);
     
        var spattern:String = listData.label;
     
        var tic:TextImageClass = new TextImageClass(spattern);
        if (tic.tranform()) {
            this.setStyle("paddingRight",Number(35));
            if (tic.displayObject != null) {
                this.setStyle("icon",tic.imageClass);
                this.labelPlacement = ButtonLabelPlacement.LEFT;
            }
            if (tic.uiTextField != null) {
                this.label = tic.uiTextField.text;
            }
        }
    }

    my problem is when i sort a column. When i click the column header, the data rows are changed but not sorted… but i move the mouse cursor over the rows… magically… the data in the columns are going to appearing sorted.

    any idea?
    thanks, congratulations, it´s a great website

  15. Hello,

    Thank for this very interesting example.

    If a change the source with an arraycollection, what kind of modifications i must do in the PriceLabel.as field ?

    Thanks

    Best Regards

  16. Pierre,

    In PriceLabel.as, it looks like I hard-coded the column data field (data.@price) into the item renderer. Of course, you could create a new subclass for each different data field (CostLabel.as, etc.), but that would probably not be the best approach.

    I think the better approach would be to look at my coworker Alex Harui’s solution to this, as he is ridiculously smarter than I am: “Thinking About Item Renderers”. Specifically, I was reading the Text Color and Styles in Item Renderers section. He shows how you can extend both the DataGridColumn and DataGridItemRenderer classes, and create a custom stylesFunction() method which evaluates the data in a column without hard-coding specific column names.

    Peter

  17. I’m trying to use similar code but have the logic based on the value of an attribute “D” in my xml data but I cannot figure out for the life of me how to access the attribute in the .as file for the current column (I’m using the renderer for multiple columns). My XML looks something like this:

    <cols>
        <colA D="1">123</colA>
        <colB D="2">123</colB>
    </cols>

    using data..@D gives all D values for the current cols node (i.e. “12″). Can anyone advise?

    Thanks!

  18. I have try your example.Very useful.Thanks lots. I still have one question and hope you can help. Now I have 2 string, lets say AAA and BBB which I want to change their color out of many data. I try to use :

     setStyle("color", (data.id == "AAA") ? NEGATIVE_COLOR : POSITIVE_COLOR);
     setStyle("color", (data.id == "BBB") ? NEGATIVE_COLOR : POSITIVE_COLOR);

    but it only change the color of BBB.
    So what should I do to change both field?
    thanks

  19. Hi,

    Very nice blog … Is there any way to change the text color of the entire row of a datagrid based on data with out creating a itemrenderer for each column. I have around 10 columns, and want to change the color of text to red or green based on some data.

    Thanks,
    Shreyas

  20. Is there any concern of this method being a performance hit? I put a trace statement in my updateDisplayList() in my custom component while I was getting things going. I noticed that if I had 10 items, on load i got 20-30 traces. If I move my mouse over I get more traces. Same with a select. I understand why this happens, but is there not a way to set it and forget it? Does the DG loose the settings each time it redraws?? I am still sort of amazed that there isn’t just a property to set for a row color.

  21. Hi peterd
    Instead of comparing the data value with 0, If i want to do comparison of two column value and set the row value red if column1 value is less than column2 value then how can i do?

    1. @Niladri,

      Try something like this:

      <?xml version="1.0" encoding="utf-8"?>
      <!-- http://blog.flexexamples.com/2007/08/20/formatting-a-flex-datagrid-control-using-a-custom-item-renderer/ -->
      <mx:Application name="DataGridColumn_itemRenderer_test"
              xmlns:mx="http://www.adobe.com/2006/mxml"
              layout="vertical"
              verticalAlign="middle"
              backgroundColor="white">
       
          <mx:Script>
              <![CDATA[
                  import mx.utils.ObjectUtil;
       
                  private function col1_sortCompareFunc(itemA:Object, itemB:Object):int {
                      return ObjectUtil.numericCompare(itemA.@col1, itemB.@col1);
                  }
       
                  private function col2_sortCompareFunc(itemA:Object, itemB:Object):int {
                      return ObjectUtil.numericCompare(itemA.@col2, itemB.@col2);
                  }
              ]]>
          </mx:Script>
       
          <mx:XML id="itemsXML">
              <items>
                  <item name="Item 1" col1="1" col2="0" />
                  <item name="Item 2" col1="0" col2="1" />
                  <item name="Item 3" col1="0" col2="0" />
                  <item name="Item 4" col1="-4" col2="44" />
                  <item name="Item 4" col1="44" col2="-4" />
                  <item name="Item 4" col1="2" col2="111" />
                  <item name="Item 4" col1="2" col2="1" />
                  <item name="Item 4" col1="1" col2="222" />
              </items>
          </mx:XML>
       
          <mx:DataGrid id="dataGrid"
                  dataProvider="{itemsXML.item}"
                  rowCount="{dataGrid.dataProvider.length}">
              <mx:columns>
                  <mx:DataGridColumn dataField="@name"
                          headerText="Name:" />
       
                  <mx:DataGridColumn dataField="@col1"
                          headerText="Col1:"
                          sortCompareFunction="col1_sortCompareFunc"
                          itemRenderer="PriceLabel" />
       
                  <mx:DataGridColumn dataField="@col2"
                          headerText="Col2:"
                          sortCompareFunction="col2_sortCompareFunc"
                          itemRenderer="PriceLabel" />
              </mx:columns>
          </mx:DataGrid>
       
      </mx:Application>

      And the custom item renderer, PriceLabel.as, is as follows:

      /** http://blog.flexexamples.com/2007/08/20/formatting-a-flex-datagrid-control-using-a-custom-item-renderer/ */
      package {
          import mx.controls.Label;
          import mx.controls.listClasses.*;
       
          public class PriceLabel extends Label {
       
              private const POSITIVE_COLOR:uint = 0x000000; // Black
              private const NEGATIVE_COLOR:uint = 0xFF0000; // Red
       
              override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void {
                  super.updateDisplayList(unscaledWidth, unscaledHeight);
       
                  /* Set the font color based on the item price. */
                  setStyle("color", (parseFloat(data.@col1) < parseFloat(data.@col2)) ? NEGATIVE_COLOR : POSITIVE_COLOR);
              }
          }
      }

      Peter

  22. Peter, I have one doubt.I’m allocating data field for the data grid column dynamically. So some time it will not have data field as col1,may be some time it will have column field as col3 or col4.So my question is instead of referring data field in PriceLabel.as can I use the column id of those two column in PriceLabel.as

  23. I would like to know if I could pass some objects to the constructor of the customItemRender class because I wasnt able to do it, the compiler throws an error. I attach my code below:
    customItemRender class:

    package core {
    import mx.containers.VBox;
    import mx.controls.TextInput;

    public class customItemRender extends VBox {
    public function customItemRender(_TextInput:TextInput, _TextInput2:TextInput){
    //TODO: implement function
    super.addChild(_TextInput);
    super.addChild(_TextInput2);
    }
    }
    }

    Actionscript code for creating an AdvancedDataGrid itemRender:

    AdvancedDataGridColumn.itemRenderer = new ClassFactory(customItemRender(_TextInput1,_TextInput2));

    Thanks in advance for your help,

    Regards Javier

  24. Thanks for the awesome example, I think the fact that you’re still getting comments on a two year old post speaks to the usefulness of it.

    I have multiple columns that I need to run through a label function, can you think of a quick way to make the label function re-usable?

    TIA,

    ~S

  25. Never mind, I got it:

    private function cur_labelFunc(item:Object, column:DataGridColumn):String {
                    return cF.format(item[column.dataField]);
                }

    See any problems with that?

    Thanks again,

    ~S

  26. This is a good example but ItemRenderer works only with small amount of data. If you have a huge advanceddatagrid like say 50 columns and 1500 rows and you try to use an itemRenderer on it , your application will be really slow when you try to scroll horizontally or vertically. I had a similar advanced datagrid where I used itemrenderer and setStyle to show +ve numbers in blue and -ve numbers in red and it runs very slow.Let me know if you have a better solution to this problem or make it run faster. Try it when the data is getting updated every 10 seconds

  27. Thanks for your example and the nice comments there.
    I am facing a problem while trying to change the background. I am trying to change the background of specific cells that pass conditions. Like here I am testing if I have start Letter then I am testing if it delivered to the group leader or not by testing the dates of Start Letter and system date. Then if it is already sent to group leader, check if the group leader approved it or not. If not check the difference between the dates of the system date and the sent to group leader date. If the result is greater than 15 and less than 30, color that cell with red color. The other cells that are not used like manager will be in gray.
    If you could help me with this problem.
    My code is the following:

    package com.aramco.easd.ats.vo
    {
    import flash.display.Graphics;

    import mx.controls.DataGrid;
    import mx.controls.Label;
    import mx.controls.dataGridClasses.*;

    import mx.utils.StringUtil;

    [Style(name="backgroundColor", type="uint", format="Color", inherit="no")]

    public class BackgroundColor extends Label
    {
    public var g:Graphics;
    public function BackgroundColor()
    {
    super();
    }

    override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
    {
    super.updateDisplayList(unscaledWidth, unscaledHeight);
    g = graphics;
    g.clear();
    var grid1:DataGrid = DataGrid(DataGridListData(listData).owner);

    g = graphics;
    g.clear();
    unscaledWidth = this.unscaledWidth;
    unscaledHeight = this.unscaledHeight;

    if (grid1.isItemSelected(data) || grid1.isItemHighlighted(data))
    return;

    checkStageLevel();

    } // end of override function
    public function checkStageLevel():void
    {
    var systdate:Date = new Date;

    // is the letter send or not to group leader
    if ((data.tmldSentDt == null || StringUtil.trim(data.tmldSentDt) == “”) && (data.taskStaDt != null || StringUtil.trim(data.taskStaDt) != “”))
    {
    checkDurationForStage(systdate, convertMyDate(data.taskStaDt));
    }// is the letter approved by group leader or waiting him/her to approve it
    if ((data.tmldApvDt == null || StringUtil.trim(data.tmldApvDt) == “”) && (data.tmldSentDt != null || StringUtil.trim(data.tmldSentDt) != “”))
    {
    checkDurationForStage(systdate, convertMyDate(data.tmldSentDt));
    }// is the letter approved by division head or waiting him/her to approve it

    if(data.ltrdesc == “Staking” || data.ltrdesc == ” Survey)
    {
    if ((data. mgraSentDt == data. mgraSentDt || StringUtil.trim(data.dvhwSentDt) == “”) && (data[DataGridListData(listData).dataField] == “mgraSentDt” || StringUtil.trim(data[DataGridListData(listData).dataField]) == “mgraSentDt”))
    {
    g.beginFill(0xA9A9A9);
    g.drawRect(0, 0, unscaledWidth, unscaledHeight);
    g.endFill();
    }
    if ((data. mgraApvDt == null || StringUtil.trim(data. MgraApvDt) == “”) && (data[DataGridListData(listData).dataField] == “mgraApvDt” || StringUtil.trim(data[DataGridListData(listData).dataField]) == “mgraApvDt”))
    {
    g.beginFill(0xA9A9A9);
    g.drawRect(0, 0, unscaledWidth, unscaledHeight);
    g.endFill();
    }
    }

    public function convertMyDate(strToCheck:String):Date {
    var uryear:String;
    var urmonth:String;
    var urday:String;
    var newDate:Date;

    uryear = strToCheck;
    if (uryear != null)
    uryear = uryear.substr(uryear.length – 4, 4);

    urmonth = strToCheck;
    if (urmonth != null)
    urmonth = urmonth.substr(0, urmonth.indexOf(“/”));

    urday = strToCheck; urday = urday.substr(0, urday.length – 5);
    if (urday != null)
    urday = urday.substr(3, urday.indexOf(“/”)+1);

    newDate = new Date(Number(uryear),Number(urmonth)-1,Number(urday));

    return(newDate);
    }

    public function checkDurationForStage(systdate:Date, VariableDate:Date):void
    {
    var ResultSysdate_VariableDate:Number = fromDates(VariableDate,systdate);

    if ( 15 < ResultSysdate_VariableDate && ResultSysdate_VariableDate <= 30 && data[DataGridListData(listData).dataField] == data[DataGridListData(listData).dataField] )
    {
    g.beginFill(0xFF1234);
    g.drawRect(0, 0, unscaledWidth, unscaledHeight);
    g.endFill();
    }
    else if ( 30 < ResultSysdate_VariableDate && ResultSysdate_VariableDate <= 50 && data[DataGridListData(listData).dataField] == data[DataGridListData(listData).dataField] )
    {
    g.beginFill(0xFF1111);
    g.drawRect(0, 0, unscaledWidth, unscaledHeight);
    g.endFill();
    }
    }

    public static function fromDates(start:Date, end:Date):Number
    {
    return Math.ceil((end.time – start.time)/MILLISECONDS_IN_DAY);
    } // end of fromDates function

  28. Thanks a lot,
    Helped great .
    This is my Dictionary for flex.
    :)
    Regards,
    Sankara narayanan Ekambaranathan.

  29. Hi,
    I have a problem with a data grid.
    Rows in my data grid display data with line breaks so if a text content is composed by 3 lines my row height expand in order to display all content.
    The problem is that the height of rows from what I see depends form the first row height but in my case sometimes first row has just one line and the third has 3 lines (but it’s formatted as the first with one line height).
    Hope you understand…
    Thanks.

  30. Hi ,

    I have a AdvancedDatagrid having a dataprovider. In one of the column there is a dropdownList (rendered) having a different data provider. On change of the dropdownlist i want to change the labels of one of the columns of Grid .
    how can this be done ?
    can you please reply ?

    Thanks

Comments are closed.