Item Renderers in DataGrids - A Primer for Predictable Behavior
ItemRenderers within DataGrids in Flex seem to be a topic that is often discussed and often struggled with. This is my attempt at a short and sweet explanation/example to get newcomers up-to-speed. This will also address the issues many people have with inconsistent rendering during scrolling and sorting of their DataGrid. Check out this example (right-click for source) and do as much clicking, scrolling and sorting as you want. Things should stay in the correct state regardless of how hard you bang on it.
There are three basic parts involved in successfully implementing IRs in DGs. The first is dispatching an event when the user interacts with the IR. This is a pretty straightforward step, but there do seem to be some differing opinions on exactly what and how information should be attached to the event. Let's look at my IR component, SimpleItemRenderer.mxml.
<mx:HBox xmlns:mx="http://www.adobe.com/2006/mxml" width="100%" height="100%" horizontalAlign="center">
<mx:Script>
<![CDATA[
// this override is essential for preventing random (un)checking when your DataGrid is scrolled
override public function set data(value:Object):void
{
// not sure of the details on when or why, but sometimes this method is passed a null value
if(value != null)
{
super.data = value;
// set itemRenderer's state based on data value
cb.selected = value.isKnown;
}
}
// local click handler that dispatches the event
// so it can be handled in a more appropriate place (such as the document holding the DataGrid)
private function onClick(e:MouseEvent):void
{
// attach our VO onto the event so that it can be manipulated in the handler
// wherever that handler may be
var evt:SimpleClickEvent = new SimpleClickEvent(super.data);
dispatchEvent(evt);
}
]]>
</mx:Script>
<mx:CheckBox id="cb" click="onClick(event)" width="15"/>
</mx:HBox>
As you can see, it is simply a CheckBox inside of an HBox. Using an HBox allows me to center the CheckBox and take advantage of other layout conveniences if I need to. I've declared a click handler for the CheckBox but its purpose is really just to package whatever data we need and pass it up the chain via dispatchEvent. You'll notice a reference to super.data, which refers to the item from your dataProvider that was passed to this row of the DataGrid. If we weren't worried about things like loose coupling we could simply update our data from here and be done with it. However, to promote modularity we will simply attach this data to our SimpleClickEvent and let it bubble up to a component that is better suited to handle the user interaction and any associated logic.
That better suited component is usually the container that is holding your DataGrid. Here is the event handler that will ultimately be fired when a user clicks on our IR's CheckBox:
// that will bubble up from the Checkbox in our DataGrid's itemRenderer
private function onSimpleClickEvent(e:SimpleClickEvent):void
{
// create refs to the items we care about
var ir:SimpleItemRenderer = e.target as SimpleItemRenderer;
var cb:CheckBox = ir.cb as CheckBox;
var personVO:PersonVO = e.vo as PersonVO;
// update the appropriate data property based on the CheckBox's selected state
if(cb.selected)
{
personVO.isKnown = true;
}
else
{
personVO.isKnown = false;
}
}
The important things to note here are that we are finally checking the type of our event's data payload, which is PersonVO. In our SimpleItemRenderer and SimpleClickEvent we avoided depending on any specific type so that they can be reused in other contexts. We then update the appropriate value in our dataProvider based on the state of our CheckBox. This is an essential step, as it records what the on-record value of this item should be for future reference.
The third and final step in our (very) basic implementation will be one such future reference and is necessary to maintain predictable behavior in the ItemRenderer, particularly if the DataGrid scrolls. This is because of the way DataGrids actually work behind-the-scenes. If your DataGrid's dataProvider has 100 items, but it is only big enough to display 10 at any given time, the DataGrid only actually draws 10 items, in order to maximize performance. When you scroll the DataGrid there aren't any new items being drawn, it is simply swapping the data properties between the already drawn items. So scrolling down one row means that the piece of data for the second item is switched to be the data for the first item, the second item receives the third item's data, and so on and so on. During these reassignments, things like whether or not the CheckBox should be checked get all messed up. Some get checked, some get unchecked... its completely unpredictable. To handle this problem, we must override the data's setter method and use our own logic to determine whether or not the CheckBox should be checked. Here is the relevant piece from SimpleItemRenderer (also shown above).
override public function set data(value:Object):void
{
// not sure of the details on when or why, but sometimes this method is passed a null value
if(value != null)
{
super.data = value;
// set itemRenderer's state based on data value
cb.selected = value.isKnown;
}
}
Yes, this is somewhat tightly coupled to this particular usage since we are assuming value has an isKnown property. I haven't figured out the best way to get around this dependency yet but am leaning towards creating an interface or a generic superclass and having all of my ItemRenderers implement/extend it. To see the scrolling wackiness in action just comment out this method.
OK, so I lied. This definitely wasn't a short explanation, but hopefully it will prove thorough and clear enough to help someone else get their head around using ItemRenderers in Flex.


on October 27th, 2006 at 3:04 pm
[...] Comportamiento predecible en ItemRenderers El siguiente link en return undefined; os llevará a un artículo muy esclarecedor sobre como usar ItemRenderers con DataGrids obteniendo un comportamiento predecible. Sin duda es de obligada lectura : [...]
on November 2nd, 2006 at 7:27 pm
Hey Ben, thanks for your post (and for referencing mine I guess). While I'm still dealing with a lot of headaches regarding DataGrid's and ItemRenderers I'm getting closer to having things working. I feel like I refactor my grid every day attempting a new method of doing things. Sometimes I get closer to having things work, sometimes not.
My latest work includes attempting to get a CheckBox ItemRenderer (similar to yours) to communicate when it's clicked so I can act on the click in enabling another columns ComboBox ItemRenderer. I'm going to adjust my custom event code (and listener) and see if I can get close to what I need. At some point I will be blogging a follow-up post, similar to your post here, showcasing what I finally get working. I've definitely had a love/hate relationship with Flex. On so many levels it is cool, productive, and easier than say, building an app in the Flash IDE. It's also very, very frustrating working through issues. I've yet to find one good, solid, accurate resource for doing custom things with grids (like ItemRenderers). Instead, I've had to mash up lots of info from various resources - some of which has just been blatently incorrect.
Anyhow, thanks for your post, I certainly appreciate it.
on November 2nd, 2006 at 9:57 pm
No problem Aaron, glad I could help. Your scenario of one ItemRenderer affecting another one is interesting and something I hadn't thought of, but if I am understanding the problem correctly, I think there is a fairly simple solution. Couldn't you just bind the ComboBox's enabled property to whatever value you set in the handler of your CheckBox? So for example, in my code above you would bind to personVO.isKnown. I don't have time to try out any code tonight, but let me know if I am accurately describing your situation and I will see if I can put something together.
on November 3rd, 2006 at 12:12 pm
You're pretty much right on. The CheckBox ItemRenderer (currently built as an AS class/component) dispatches a custom event each time it is clicked. The event bubbles up through the grid and gets handled in the main application. Each row has a disabled ComboBox that gets prefilled with data from the server. If the data from the server has the appropriate CheckBox checked then I need the ComboBox to be enabled and I'll need to pre-select the correct item in the box. So, for instance, if my grid holds employees the CheckBox column might refer to active employees. The ComboBox might hold all the departments in the company. If an employee is active the ComboBox should be enabled and be pre-selected with the department the employee is assigned. Selecting and de-selecting the "active" CheckBox only affects whether the ComboBox ItemEditor is on or off. If on, the user can change the employees department association.
My scenario is not employees and departments but it's an easier way to describe what I'm trying to do. I'm still having some really weird issues with my CheckBox ItemRenderer and the "click" event dispatching. Today I'm going to adjust how I do things to incorporate some of your ideas and see if it makes things any better. Then, it's on to the activation and selecting of the ComboBox.
Thanks Ben.
on November 13th, 2006 at 6:27 pm
Is there an example of rendering a ComboBox inside a Datagrid?
on November 13th, 2006 at 10:22 pm
Hi Richard,
It should be a pretty simple transition. I've not tested any of this but the following code should at least get you close.
becomes
Adjust your data override function:
override public function set data(value:Object):void
{
// not sure of the details on when or why, but sometimes this method is passed a null value
if(value != null)
{
super.data = value;
// set itemRenderer's state based on data value
combo.selectedItem = value;
}
}
I will be posting a new article shortly that shows how to create truly decoupled renderers that may help as well. Check back soon!
on November 14th, 2006 at 10:38 am
[...] In my last post, I outlined some ways to ensure that your item renderers behave predictably. In that article, however, I admitted that I had yet to figure out the best way to create renderers that were completely decoupled from the data they represented and therefore wholly reusable across projects. As of yesterday afternoon, I believe that has changed. I have extended my previous example to demonstrate, and the new version can be seen here. Right-click for source. [...]
on November 22nd, 2006 at 6:20 am
Hi Ben / All,
I have recently started working with Flex 2; like Aaron, I am developing a bit of a love / hate relationship too, coming from Java's JTable.getCellReneder(i, j) programming model.
At the time of writing, the link to your updated example was not available / broken, so forgive me if I am not sharing anything new.
DataGrids use IDropInListItemRenderers; similar to how the IDataRenderer declares the 'data' property, IDropInListItemRenderer declares the 'listData' property. It is the listData property that should enable low-coupling. The listData is of type BaseListData, which has a useful 'label' property that actually contains the data to be rendered i.e. listData.label is basically equivalent to String(value.isKnown) in your example.
Where your custom ItemRenderer is 'dropped-in' to a DataGrid, listData is actually an instance of DataGridListData, which is a subclass of BaseListData. DataGridListData conveniently provides a 'dataField' property that, quoting form the API, provides the 'Name of the field or property in the data provider associated with the column, e.g. isKnown.
In addition to the properties mentioned above, BaseListData provides 'rowIndex' and DataGridListData provides 'columnIndex'; whether the use of this information will manifest as high-coupling is for the programmer to decide - the point is that the meta is there and accessible, thus making Flex DataGrids more... flexible.
As for propagating information such as CheckBox clicks, try using the DataGridEvents. I have a custom DataGrid with an artificial column, that is a column that is not represented in the data model, which is renderered as two buttons. When one of these buttons is clicked, it dispatches a 'DataGridEvent.ITEM_EDIT_BEGINNING' event which is handled by a custom 'DataGrid.itemEditBeginning' event handler; this is implemented in my custom DataGrid but this need not be the case. The aim here is to make that row for which the button was clicked editable.
Aaron: I would be interested to know how you finally solved your problem, if you have that is?
Hope this helps.
on November 23rd, 2006 at 11:44 pm
Hi Ben,
I want an image over the datagrid row. Actually I want a glassy effect over the datagrid row for which I have a image which need to place over each row. How can I do that.
on November 24th, 2006 at 9:21 pm
Hi Arpan,
That's a pretty broad question, maybe this article can help you get started. http://www.adobe.com/devnet/flex/quickstart/using_item_renderers/#drop_in_item
Ben
on January 16th, 2007 at 8:56 pm
I'm lucky I stumbled on this. I was having major comprehension problems with the ItemRenderer in the datagrid.
on January 23rd, 2007 at 12:13 am
Hi Ben,
Thanks for such a simple working example for this problem. Well, I need some help. My application has a checkbox in the header of datagrid as well. And my requirement is that on checking that checkbox, the row check boxes should get checked. I have tried using {dataPrivider}.itemUpdated(), but it has a very strange effect. The row checkboxes get checked but the header checkbox doesn't ever get in checked state! Hope I dint confuse you:) Any help will be highly appreciated.
Thanks in advance,
Cheree
on February 8th, 2007 at 10:47 pm
Dear Ben:
Thanks a lot for your excellent article, which helps me greatly.
And I still have a question :
Would you please tell me when the "set data" method is invoked in the "SimpleItemRenderer.mxml" ??
I'm a flex newbie, thanks in advance.
on February 9th, 2007 at 12:28 am
Hi Lewis,
Since set data is a setter method, it gets called each time the value your renderer is bound to gets changed, and every time your itemRenderer is re-rendered.
HTH,
Ben
on February 9th, 2007 at 12:58 am
Hi ben,
Thanks for your reply, I made it!
on February 21st, 2007 at 6:51 pm
Go Bengals!!
Just wanted to thank you for this example and wondering if you created the combobox itemrenderer in a datagrid yet (as noted in a post above). I am new to flex too and understand fully the love/hate relationship with it. I am having problems populating my combo boxes inside datagrids dynamically using web services.
on February 22nd, 2007 at 12:24 am
Hi Doug, glad the example helped. I have not done a ComboBox example yet. I've been swamped at work, hence the lack of new posts. I have been hoping to write something soon, so maybe the ComboBox example would make a good topic. Check back soon, or better yet subscribe. :)
Thanks,
Ben
on March 9th, 2007 at 9:18 am
[...] http://www.dgrigg.com/post.cfm/10/20/2006/Flex-ItemRenderer-CheckBox-Sample http://philflash.inway.fr/flex/dgRendererSimple/index.html http://www.returnundefined.com/2006/10/item-renderers-in-datagrids-a-primer-for-predictable-behavior/ [...]
on May 3rd, 2007 at 2:03 pm
Hi Ben,
I've been looking for answers about this issue and got some good stuff with this post.
My problem is quite a bit different, I have a button Component which changes from one state to another itself choosing the state from variables from the datagrid field which is rendering. As you all know the first render works perfect, but later with just an arrange the renders get crazy. I've applied some of your code and it seems like there's some advance, now it doesn't seem scramble it renders passing by the value to the next row, and goes on with every click. That's crazy I know, but maybe you have figured this code out with more than one variable.
thanks forward.
andres
on May 11th, 2007 at 2:08 pm
Hi all!
is there any way to make it work without adding an additional member variable to object, .isKnown in this case?
Thanks
- Nam
on May 12th, 2007 at 2:07 pm
Hi Nam, I'm not sure exactly what you mean, but the next article might be of interest to you.
http://www.returnundefined.com/2006/11/creating-truly-reusable-renderers-with-classfactory/
HTH,
Ben
on June 13th, 2007 at 8:16 pm
I have a List bounded with a dataProvider and rendered with an itemRenderer. I need to display 1, 2, 3, and so on on each list item. And the list can be re-arranged via drag-n-drop and the index needs to remain constant (meaning the first item on the list will always say "1", second "2", and so on). Could I get an advice on how to achieve this? The itemRenderer is based on HBox. So it does not implement IDropInListItemRenderer, thus I don't have reference to listData to get the rowIndex.
Any help is greatly appreciated! Thanks!
on June 13th, 2007 at 11:31 pm
I would recommend implementing IDropInListItemRenderer. It looks like all you would have to do is implement a getter/setter for listData and of course add the implements="mx.controls.listClasses.IDropInListItemRenderer" attribute to your base tag. After that you're home free.
http://livedocs.adobe.com/flex/2/langref/mx/controls/listClasses/IDropInListItemRenderer.html
on June 20th, 2007 at 1:13 pm
Ben,
Great example!!
I had a question how do you get the checkbox checked if you click on the row of the datagrid and not jsut the checkbox itself. I found one example on how to do it but it only allows one check at a time: http://blog.739saintlouis.com/2007/05/14/radiobutton-datagrid-itemrenderer-that-selects-and-indicates-selection-of-entire-row/
any ideas?
Thanks
-Brett
on July 10th, 2007 at 7:58 pm
Hi Ben,
Your override of the set data really saved me a lot of frustration....well, after i found it ; )
I am running into issues (null object references in DragProxy -- in the debug player) with drag re-ordering of the datagrid now, however. When I comment out this override, the drag works...but then of course the updating of my checkbox item renderers gets wonky. Any ideas would be appreciated.
Thanks,
Philip
on September 6th, 2007 at 7:34 am
Hi Ben,
I am trying to find out a way to create an itemRenderer that will call other itemRenderer upon data Results
I am actually using an extend of the DatagridItemRenderer but can't figure out how I could include a gradient background on that Renderer.
I am planning then to create an itemRenderer where I could define if the data return is type of Label then run another Renderer.
May be you would have a better suggestion to do this. Any idea?
In any case many thanks for sharing your knowledge. Regards. Rudolf.
on September 6th, 2007 at 8:41 am
[...] Talking about flex, we have run into a behaviour with a scrolling datagrid and checkboxes.? We quickly found the solution on this site. This is another strength of Flex, it is its very dynamic and open community that I have not found since the nice days of Ruby on Rails. Posted by hleinar Filed in Hleinar [...]
on November 12th, 2007 at 10:38 am
Hi Ben!!!
I dont speak (write) english very well, then sorry :P
I have the same problem that Doug: I´m a noob with FLex and I´ve fought with a datagrid all week. I´ve a datagrid, with a itemrenderer inside a column. This itemrenderer has a component: a combobox, but its dataprovider, must fill dinamically (from webservice). Then that is my problem...
I need your help plssss, Ben
Greetings and thanks,
Niky´s
on November 16th, 2007 at 5:42 am
Hi,
I was experiencing the same problem...that is random checkbox selections appearing while sorting and clicking away and actually I found a very easy solution:
you need to set your values to "true" or "false" and not leave them to "undefined"
That's it
Katia
>>> datagrid >>>
>>> somewhere in init() function >>>
//unselect all checkboxes
for (var i:int=0; i
on November 17th, 2007 at 11:34 pm
[...] 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. [...]
on December 11th, 2007 at 3:55 am
Hi,
I only want to say: Thank you! The IR drives me cracy and now everythings fine!
Thanks...
u.
on December 27th, 2007 at 1:20 pm
Thanks for this tutorial. After a day of wrestling, I found this blog entry and was able to solve my issues in just a few minutes.
on January 14th, 2008 at 1:47 pm
While looking at this same issue with the scrolling in my own app, I came across an event handler on the base class of my itemRenderer (an HBox). The event is called dataChange. Using that I can just call a simple function that resets the data for that item renderer. It's easier than overriding the set data function. I'm using Flex 3 and I'm not sure if it's part of Flex 2 or not. Anyway. Hope this helps someone.
on March 14th, 2008 at 8:25 am
I guess this is off time but anyway..
I have a datagrid and an itemrenederer for it.On the initialize of the itemrenderer i add an event listenererr on the parent document, and the handler of it checks to see if the data of the itemrenderer is the same of the selected item of the datagrid.But the problem is that one of the itemrenderers runs this handler twice(the first or the rows from what i've noticed).So this means that the itemrenderer is created twice but it can be seen on the datagrid once.Do you know what the problem might be and to overcome this?
on March 14th, 2008 at 8:44 am
Hi Alex, I can't say I know whats going on there but as a general practice I try to avoid having my item renderers know anything about their surroundings. I think a better approach would be to simply have your IR dispatch an event that bubbles and then catch it in the same file that holds your DG. Then you can do whatever sort of checking you need to do. Using startup events from your IRs will usually be unreliable due to renderer recycling and deferred instantiation.
Hope that helps,
Ben
on March 14th, 2008 at 11:48 am
In the apps you created when you override the set data function does the renderers seem to be one more than they should be?For example when i trace something in the set data i always get one more that i expected.I read somewhere that the datagrid creates some more itemrenderers that they are not displayed(it has something to do with the recycle).
on March 14th, 2008 at 12:58 pm
Not sure what you mean by "one more". The data setter will be passed the entire data item for that row of data and I've not ever experienced any kind of offset errors.
on March 14th, 2008 at 2:36 pm
I mean that if do trace(""trace") in the set data and i have 3 items created in the datagrid list i get 4 traces trace.
on March 14th, 2008 at 2:48 pm
Ah, yes, the data property will get set and reset a variable number of times. For example, each time you scroll the list every renderer will have its data property reset because it needs to then render the next/previous data item.
on May 9th, 2008 at 9:16 am
I am using something similar to color the background of cells in de datagrid. But i want to check it with data i have in an array in my main mxml. Is there a possibility to send an array with the item renderer, Or an other option to use the array from out of the item renderer t
thx
on May 26th, 2008 at 9:36 pm
hi Ben/all,
the example given here works fine if the dataprovider has got the selected property.(in this case ,isKnown)
but what if there is no property for checkbox selection in the dataprovider?.
And if i select the checkbox ,its state should remain the same.how can i set the checkbox selected value??
thanks
on June 25th, 2008 at 5:05 pm
Hi Ben, just wanted to say thanks for the kick*ss post - it really helped me out. Keep up the awesome work (so people like me don't end up tearing our hair out)!! :-)