return undefined;


Efficient, reusable (and centered) CheckBox renderers for DataGrids

Posted in AS3, Flex by Ben Clinkinbeard on the November 16th, 2007

As it turns out, truly reusable CheckBox renderers are much simpler to create than I previously reported. This post will provide and discuss what I think are the last CheckBox renderers you will ever need.

Did you know that CheckBox has a built-in click handler? I didn't until recently, despite its not-so-sneaky name of clickHandler. It is a protected function, so its only accessible if you subclass CheckBox, but that's OK since our renderers will do just that. clickHandler is key to creating these reusable renderers and keeping them super simple.

Before I start in on my inevitably wordy and detailed description lets take a look at our end result. CheckBoxRenderers in action and the source.

Edit: I initially forgot to mention that these renderers are heavily based on and influenced by the information provided by Flex Jedi Alex Harui on his blog. Thanks to Alex for illustrating "the right way" to do things.

This time around we will start with the item renderer since it is the simpler of the two. (Both of these classes center the CheckBox as that is the most common request, but if you don't need them centered simply remove the updateDisplayList() functions.)

CenteredCheckBoxItemRenderer.as:

Actionscript:
  1. package com.returnundefined.view.renderers
  2. {
  3.     import flash.display.DisplayObject;
  4.     import flash.events.MouseEvent;
  5.     import flash.text.TextField;
  6.    
  7.     import mx.controls.CheckBox;
  8.     import mx.controls.dataGridClasses.DataGridListData;
  9.  
  10.     public class CenteredCheckBoxItemRenderer extends CheckBox
  11.     {
  12.         // update data item on click
  13.         override protected function clickHandler(event:MouseEvent):void
  14.         {
  15.             super.clickHandler(event);
  16.             data[DataGridListData(listData).dataField] = selected;
  17.         }
  18.        
  19.         // center the checkbox icon
  20.         override protected function updateDisplayList(w:Number, h:Number):void
  21.         {
  22.             super.updateDisplayList(w, h);
  23.            
  24.             var n:int = numChildren;
  25.             for (var i:int = 0; i <n; i++)
  26.             {
  27.                 var c:DisplayObject = getChildAt(i);
  28.                 // CheckBox component is made up of box skin and label TextField
  29.                 if (!(c is TextField))
  30.                 {
  31.                     c.x = (w - c.width) / 2;
  32.                     c.y = (h - c.height) / 2;
  33.                 }
  34.             }
  35.         }
  36.     }
  37. }

There are 2 major differences between this class and our previous version. First, it doesn't implement ClassFactory, which means we can simply type its fully qualified class name into the itemRenderer attribute of a DataGrid and it will work (just like with mx.controls.CheckBox), requiring one less bindable variable in our file. The reason we can do this is that CheckBox, and by extension CenteredCheckBoxItemRenderer implement IDropInListItemRenderer. I won't go into the full details (if anyone would like me to dedicate a post to describing this more fully let me know) but in the case of CheckBox, that basically means that it knows how to set its selected state based on a piece of data that is passed to it. The second major difference is closely related to this fact, which is that we no longer have to override the data setter- we get that functionality for free.

The most important line in the item renderer class is essentially the other half of the equation. Whereas we get data retrieval and rendering for free, we have to provide the data setting functionality. That is really the only thing that separates it from a regular CheckBox other than the centering code, and its accomplished with this single line of code:

data[DataGridListData(listData).dataField] = selected;

This line uses the listData property (defined by IDropInListItemRenderer) to locate and update the specific piece of data this CheckBox is rendering in our DataGrid.

The CenteredCheckBoxHeaderRender class is pretty similar to my previous version in that it still implements IFactory and has to be implemented as a ClassFactory instance. For details on using ClassFactory see the previous post referenced above. The main differences this time around are that the class is defined in ActionScript rather than MXML and streamlines things by using the built-in clickHandler function. Below is the portion of the class we will discuss further.

Actionscript:
  1. // these vars are used to reference the external property that stores our selected state
  2. public var stateHost:Object;
  3. public var stateProperty:String;
  4.  
  5. // set selected state based on external property
  6. override public function set data(value:Object):void
  7. {
  8.     selected = stateHost[stateProperty];
  9. }
  10.  
  11. // toggle external property on click
  12. override protected function clickHandler(event:MouseEvent):void
  13. {
  14.     super.clickHandler(event);
  15.     stateHost[stateProperty] = selected;
  16. }

As discussed in the previous post, headerRenderers get initialized repeatedly during their existence. This means that we are unable to store any kind of state for them inside themselves because it will get reset over and over again. To work around this we define the stateHost and stateProperty variables which will be assigned via the properties property of ClassFactory. (Again, see previous post for a more thorough explanation of ClassFactory.) In our example application we point these to a selectAllFlag variable (the stateProperty) defined in the main application (the stateHost property). The overridden data setter shown above then uses this external value to set the selected state of our component. Correspondingly, clickHandler sets this value when our CheckBox is clicked, saving our state out to a safe, external location.

Two minor things to mention about our application are a couple of attributes set on the DataGridColumn that our renderers live in. First, notice that we set a dataField attribute like usual. This is required for our new itemRenderer class to work properly, whereas it was not needed when using ClassFactory for both the headerRenderer and itemRenderer. The second important point is that we have set the column's sortable attribute to false. This is necessary in order to ensure clicks in the header are processed as CheckBox clicks and not sorting actions. Also note that we process the header click and set the item values in the main file. While it would be possible to do this inside the header renderer class leaving it outside makes the renderer that much more flexible.

Hopefully this all makes sense, is not too long winded and provides renderers that others can and will use in future projects. Please post questions, complaints and comments in the... well... comments.

5 Responses to 'Efficient, reusable (and centered) CheckBox renderers for DataGrids'

Subscribe to comments with RSS or TrackBack to 'Efficient, reusable (and centered) CheckBox renderers for DataGrids'.


  1. on November 16th, 2007 at 4:38 pm

    [...] Update: I have come up with better solutions since writing this post. This post is still worth a read but please be sure to check out this post as well. [...]

  2. Bob Keleher said,

    on November 20th, 2007 at 4:51 pm

    Hi Ben,
    Your code seems to be just what I need but there is one issue that I don't know how to overcome.

    On my first attempt to adapt the code, I found that when I clicked on a checkbox instead of checking/unchecking it, it would simply display the underlying value, e.g. false. My datagrid was set to editable="true". In order to make the checkbox code work as expected I found that I had to set either the datagrid or the checkbox column to editable="false" . But doing that means that I can no longer access an itemEditEnd event.

    I need to take certain actions based on changes to datafields. Any suggestions as to how I can do that while using your checkbox code?

    Thanks

    Bob

  3. Ben said,

    on November 20th, 2007 at 5:41 pm

    Hi Bob,

    Reading through it now there are a few things I wish I had explained better in the article. The only real difference between mine and Alex's renderer is that mine automatically sets the value in your dataProvider to true or false, based on the selected state of the CheckBox. You will definitely want to keep editable set to false, but you can still get notifications when values change. As I mentioned in the article, the click event will still bubble up, so you could easily listen for MouseEvent.CLICK, check to make sure event.target is a CenteredCheckBoxItemRenderer, and then take any appropriate action.

    Hope that helps, let me know if its still not clear.

    Ben

  4. Saber said,

    on November 29th, 2007 at 7:35 am

    Hi Ben,

    Your code seems to be just what I need but there is one problem that I don't know how to overcome.

    On my first attempt to adapt the code to my application, I introduced a combobox to filter the datagrid provider and after filtering some rows, I clicked on the checkbox header to check/uncheck all rows, but doing that it would simply check/uncheck some of them !.

    Any suggestions as to how I can resolve this while using your checkbox code?

    Thanks in advance.

    Saber.

  5. hongjie said,

    on March 30th, 2008 at 2:34 am

    Dear Ben,

    Thanks for your CheckBox render. I have used it in my project. However, I found that if the row height was more than two lines, the updateDisplayList function does not work properly. I modified the updateDisplayList function of CenteredCheckBoxItemRenderer as follows, then it works fine:

    if (!(c is TextField))
    {
    c.x = (w - c.width) / 2;
    c.y = 0;
    ^^^^^^^^^^
    }

    However, I have another problem that if the datagrid reload data from its data provider and the head was selected already, then the items of the DataGrid will unselected but the head remain selected. How do I fix this?

    Thanks a lot.

Leave a Reply