Creating truly reusable renderers with ClassFactory
Update: I have come up with better solutions alternate approaches since writing this post. For a more compact and self-contained approach check out this post as well.
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.
My current project requires a DataGrid with the commonly seen select/unselect all functionality. The design depicts this simply as a column that is a centered CheckBox as both the itemRenderer and headerRenderer. When you click the CheckBox in the header, it toggles the state of all the items to match its own. I first addressed the item renderers. I managed to create a component that was completely decoupled (hooray!), predictable, and easy to use. It implemented the IDropInListItemRenderer interface, which allowed it to be tied to the appropriate property in its data item through the familiar dataField attribute of DataGridColumn. When I moved on to implement the headerRenderer, however, I ran into a bit of a problem. This turned out to be a blessing in disguise though, as it led me to the real star of the show here: ClassFactory. ClassFactory essentially just allows you to specify a class (that implements IFactory), specify some properties for it, and then assign it as the itemRenderer or headerRenderer for your column.
The headerRenderer is probably the simpler of the two, so I'll go into more detail on that first. The problems I initially had with my headerRenderer were related to the fact that headerRenderers seem to be recreated, or at least reinitialized, every time there is an event in the DataGrid in which they live. (If anyone can more accurately explain how they behave please post in the comments.) As a result, you must bind their state to a value that is held outside of said DataGrid. In my case, I called this variable allSelected. You also need to set up the ClassFactory variable that you will assign as the headerRenderer. Here is what that code looks like:
...
checkBoxHeaderRenderer = new ClassFactory(GenericCheckBoxHeaderRenderer);
checkBoxHeaderRenderer.properties = {externalObject: this, externalPropertyName: "allSelected"};
This tells Flex to use the GenericCheckBoxHeaderRenderer class as the renderer, and to set 2 properties on each instance it creates. It should be apparent that I am giving each instance a reference to the allSelected property mentioned above. Here is what our GenericCheckBoxHeaderRenderer class looks like.
<mx:HBox xmlns:mx="http://www.adobe.com/2006/mxml" width="100%" height="100%" horizontalAlign="center" preinitialize="init()" implements="mx.core.IFactory">
<mx:Script>
<![CDATA[
import mx.binding.utils.BindingUtils;
// will be set by "properties" property of ClassFactory
// allows us to bind to an external value while remaining decoupled
public var externalObject:*;
public var externalPropertyName:String;
// local property that will be bound to external value held in externalObject[externalPropertyName]
// CheckBox's selected property in turn bound to this
[Bindable] public var isSelected:Boolean;
// method required by IFactory
public function newInstance():*
{
return new GenericCheckBoxHeaderRenderer();
}
// set up binding of local property to value specified by ClassFactory::properties
private function init():void
{
BindingUtils.bindProperty(this, "isSelected", externalObject, externalPropertyName);
}
// 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(event:MouseEvent):void
{
var evt:SimpleHeaderClickEvent = new SimpleHeaderClickEvent(cb);
dispatchEvent(evt);
}
]]>
</mx:Script>
<mx:CheckBox id="cb" click="onClick(event)" selected="{isSelected}" width="15"/>
</mx:HBox>
One thing to note in that class is that I have created a local variable that is bound to the external value (allSelected) that is passed in via the properties property, and my CheckBox then binds to that local value.
The itemRenderer version of this code is very similar, the main difference being that it is tied to a specific property on the data object that is passed to it in the list rather than a single, external value. As a result, we only need to pass it one value, that being the name of the property to which we want to tie the renderer. The ClassFactory variable is set up in the same fashion:
...
checkBoxItemRenderer = new ClassFactory(GenericCheckBoxItemRenderer);
checkBoxItemRenderer.properties = {dataField: "isKnown"};
and the GenericCheckBoxItemRenderer class is also similar:
<mx:HBox xmlns:mx="http://www.adobe.com/2006/mxml" width="100%" height="100%" horizontalAlign="center" implements="mx.core.IFactory">
<mx:Script>
<![CDATA[
import mx.controls.dataGridClasses.DataGridColumn;
// name of property on our VO we're concerned with
public var dataField:String;
// method required by IFactory
public function newInstance():*
{
return new GenericCheckBoxItemRenderer();
}
// 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;
// the parent DataGridColumn is passed as value before the real data arrives
if(!(value is DataGridColumn))
{
// set itemRenderer's state based on the value held in the property specified by _dataField
cb.selected = value[dataField];
}
}
}
// 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 and _dataField onto the event so that it can be manipulated in the handler
// wherever that handler may be
var evt:SimpleItemClickEvent = new SimpleItemClickEvent(super.data, dataField);
dispatchEvent(evt);
}
]]>
</mx:Script>
<mx:CheckBox id="cb" click="onClick(event)" width="15"/>
</mx:HBox>
Here you can see that the override method no longer relies on any specific property (and therefore datatype), instead using the property specified by dataField on whatever type of object is passed to the itemRenderer by the dataProvider.
And that's my system. If you look at the source code you can see that we still handle the events in the parent file of our DataGrid, but note that these event handlers are the only place where we are tightly coupled to specific properties and/or data types, which is exactly what we want. We have moved all type references out of our renderers, making them completely reusable across varying parts of a project or in different projects altogether. It would likely be very easy to genericize the renderers even further to allow specifying what type of control should be used (CheckBox, ComboBox, etc), which property of said control you're interested in and on and on all through the properties property, but I decided not to go that far. There is something to be said for retaining semantic meaning in your class/component names and code and not extrapolating to oblivion.
Sorry these posts are so long. As it turns out, conciseness is not a trait I possess. Hopefully someone will find this information useful though, and feel free to post any questions or other thoughts in the comments. Enjoy!
You can leave a response, or trackback from your own site.

Ben:
Great article! I have run into a problem using your code in an example. For some reason, the last row’s checkboxes sometimes seem to display being Checked and enabled=false. I haven’t been able to figure out why….still troubleshooting.
Thanks for the great article though!
Sam
Thanks Sam, I’m glad you enjoyed it. Not sure why your checkboxes would be getting diabled, there definitely isn’t any code in my sample that addresses the enabled proprty. Maybe something in my previous article could help?
Ben
PS – I don’t have time to read them right now but your past articles look useful; I’ve added your site in my Bloglines account.
Very cool! I’ve been looking for a good solution to this problem. Thank you for helping folks like me. I’ve used your code against a datagrid form generated by the ColdFusion wizard and it works well when editing pre-existing data. However, when a new form is called, selecting the itemRenderer checkbox causes all other cells to become uneditable. The checkbox is still enabled, everything else is not. The headerRenderer works as advertised and does not cause any issues that I have seen.
Maybe because I’m using the actual dataprovider for the grid instead of the model? I dunno.
Hi Dan, glad you got something out of the post. I’ve never touched CF in my life, so I can’t really help you there. Also not totally sure I understand your last sentence.
Hi Ben,
I just found your updated / new article after commenting on the old one, so apologies for what I have repeated.
I will be looking into the Class/IFactory mechanism.
Cheers, Darren
No worries Darren, you provided some good information that I know a lot of people haven’t quite grasped yet. I think IDropInListItemRenderers are probably an adequate solution for a lot of scenarios, but once I discovered ClassFactory it just seemed more appropriate for what I was trying to accomplish.
One other thing to keep in mind is that rowIndex is simply the row in the DataGrid, and is not tied to the dataProvider in any way. As for Aaron’s scenario, while I was developing the example for this post I discovered that making the class that is being used in the dataProvider bindable makes it super simple to accomplish. Then you just have to modify the second data field in the click handler and the second field’s renderer should update itself.
Thanks,
Ben
Hi Ben,
I just happened to stumble across your site while doing some searching on how to use an itemRenderer. I’m still struggling a bit on using them. Would I handle a checkbox itemRenderer in the same manner if I am selecting multiple rows and using a “click” event on a button to remove the rows selected?
Thanks,
Ben
Hi Ben,
I am actually doing just that in the project I’m currently working on. As the articles show, the CheckBox itemRenderer changes a property in the dataProvider. I am assuming you mean a button located outside of the DataGrid. When you click that button you’ll just need to iterate over the dataProvider collection and use removeItem or something similar to get rid of items with the property value you’ve set. Something like this:
[as]
var ac:ArrayCollection = model.myDgDataProvider;
for each(var item:PersonVO in ac)
{
if(item.isSelected)
{
ac.removeItem(item);
}
}
ac.refresh();
[/as]
Hope that helps, let me know if it doesn’t.
Ben
So then do I still need an onClick event handler to update the dataProvider and let it know that the state has changed on each check box? I guess I’m still lost on how these work.
I have a DataGrid which is populated with an ArrayCollection which was actually converted from a Query Result to the ArrayCollection. The DataGrid has a bunch of fields, one of them being a checkbox that is a custom component and inserted into the DataGridColumn via itemRenderer.
I then have a “delete” button that sits outside of the DataGrid, when clicked a “deleteFormItems()” function is called. will my itemRenderer component have all ready updated the dataProvider to tell it which check boxes were checked so I can then loop over the dataProvider as you show in the example above?
I’m trying not to sound dumb but I really want to understand what I’m doing here and not just copy your example.
Thanks,
Ben
I think you’ve pretty much got it, Ben. You asked “do I still need an onClick event handler to update the dataProvider and let it know that the state has changed on each check box?” and “will my itemRenderer component have all ready updated the dataProvider to tell it which check boxes were checked”. The answer to both is yes. Your itemRenderer is simply a visual representation of a specific property on a specific item in your dataProvider. In fact, all cells in a DataGrid are itemRenderers, its just that the default “rendering behavior” is to display the value as text.
The main example from this article changes the isKnown property of items in the handler of the checkbox. If we wanted to delete the checked items when we click another button, we could define a deleteKnownItems() method that looks exactly like the example in my last comment, except it would look at the isKnown property instead of isSelected.
So to reiterate, you would be updating (a single item in) your dataProvider each time you click on a checkbox, and then in your button click handler you would just be looping over your dataProvider and modifying it (deleting items) whenever you find an item that meets the criteria you’re interested in. Make sense?
Ben,
Thank you very much for clearing that up for me. I think I understand it better. I just need to implement it into my App to really grasp it.
I appreciate you taking the time to explain this more to those of us that are a bit slower in absorbing new concepts.
Ben
My pleasure, Ben. It wasn’t so long ago that this was all foreign to me as well. Glad I could help!
Hi Ben,
If I’m not using a value object how can I pass a property into the ClassFactory? I have an ArrayCollection but I’m not populating a Value Object as you do. I’m still struggling a bit to get this to work with my situation because it’s not identical to your example.
Thanks,
Ben
Nevermind, I got it working. I just set a Bindable variable to default the state to false and then update the state of that variable.
Thanks for all you patience and help,
Ben
Ben,
Its a very useful article. I am trying custom components which can be used across multiple apps and this came very handy. Still struggling a bit on the way we retrieve the data from ItemRenderers. Can you point me any link/code which has this ? My ItemRenderer has a Checkbox and a Label, indicating that the label was selected, and to make it more complex, the DataGrid Columns are generated dynamically in my case
Regards,
Asgar.
Hi Asgar,
I am not quite sure I know what you mean about retrieving data from the ItemRenderers. Your IRs should be updating a model object somewhere and then that is where other things retrieve data from. If you mean that you have a label in one column that displays the state of a checkbox in another column, you would just want to put logic in the label IR’s data method override that checks the same property as the one the CB toggles.
Let me know if you have more specific questions or if I totally misunderstood what you were asking.
Thanks.
Hi everyone,
Its a very useful article. But if a DataGrid has a lot of DataGridColumn and each them have a CheckBox headerRenderer for control a column of CheckBox select/unselect.
how to get dataField in headerRenderer to indicate which column is user click ?
PS:My English learns of not good and I suppose your are not understand!
You’re right, I don’t quite understand what you mean
The click handler for the header should run the through the collection being used as the dataProvider and set whichever property it is associated with to the proper value. The collection is then refreshed and the itemRenderers update automatically.
HTH.
Hi Ben,
I know what your meaning is and I can select/unselect a column successfully,but in your code, the dataField is not automatically.
“checkBoxItemRenderer = new ClassFactory(GenericCheckBoxItemRenderer);
checkBoxItemRenderer.properties = {dataField: “isKnown”};”
What method resolve this problem that get dataField when user click a CheckBox?
I’m not sure exactly what you mean, but I will take a shot. The dataField property in the properties object is not the same thing as the dataField attribute of DataGridColumn. I suppose I could’ve named it differently to avoid confusion, but it is serving the same purpose. If you can clarify your question I will do my best to answer it.
Thanks.
Hello Ben
I’m not realy shure whether I got your problem wrong, but instead of storing the selected variable outside the datagrid, you also could take a static variable maybe inside your renderer-class. You may store the state of the CheckBox here and use it every it becomes initialized…
Cheers Pete
Hi Pete,
You could do that, but it would make your renderer class much less generic and reusable. You would need to create a separate static var for each place it is used, which could get ugly. I also think storing the value outside keeps your renderer class truer to its intended purpose of rendering, and prevents having to mix logic into a class that is really just a view component.
Ben, thanks for the example, I’m doing my best to pick up on this. Is there an easy way in the onItemClick function that I could change other column values with the event?
What I have is two numeric columns and I would like to set one to the value of the other if my checkbox column is selected, and set it to zero if it is not. Thanks for any help.
Hi Scott, you can definitely do that. The key to understanding this subject (in my opinion) is to never think about updating columns or the datagrid or similar things. Those items are nothing but a visual representation of the underlying data, which is the only thing that you should manually need to update. So assuming your dataProvider is a collection of instances of a class marked [Bindable], all you need to do is update the appropriate properties and your DataGrid columns should update accordingly.
if(cb.selected)
{
e.vo.someProperty = e.vo.someOtherProperty;
}
else
{
e.vo.someProperty = 0;
}
You need to review using containers in grids.
Alex Harui has written some blog articles that will clear some things up.
I have been hit hard by using containers in the grids. This is not a recommended method for working with grids.
http://blogs.adobe.com/aharui/2007/03/thinking_about_item_renderers_1.html
Hi “Me”,
I’m familiar with Alex’s article (Ben in the comments there is me). Not sure what you mean by “hit hard” but I’ve not had any issues using the above approach with 10 – 15 visible rows. I do agree that Alex’s approach (and pretty much everything else he says) should be required reading for Flex developers though.
Ben
Hi,
In my application I use the same check box renderer in more than one column. These check boxes are added dynamically. When ever I check the check box true, the the other checkboxes in same row are getting checked. Can anyone of you tell the solution for this problem. I dont have this problem while scrolling since I have overrriden the set data function.
Thanks in advance.
Hi Ben,
Great example!
However, when used with the new FLEX 3 SDK, it breaks. Clicking the header containing the checkbox will throw the all to familiar: “Error #1009: Cannot access a property or method of a null object reference”.
It seems that Datagrid.as (3909) cant find the renderer when trying to initiate a new DataGridEvent.ITEM_EDIT_BEGINNING.
Any solution to this?
//Morgan
Hi Morgan,
Sorry, but I don’t have time to investigate right now. I haven’t really jumped on the Flex 3 wagon yet as I’ve been pretty buried at work and with a soon-to-be-announced side project. If you find a solution please let me know, sorry I can’t offer more help.
Thanks,
Ben
It seems redundant to have such similar renderers for header and item. Is it possible to make one renderer that could do double duty?
It might be possible but I haven’t spent any time trying to accomplish that. As I mentioned in the article, the creation/update process seems to be very different for headerRenderers. Specifically, they are updated much more often than itemRenderers.
[...] 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. [...]
Hallelujah, that’s exactly what I needed. Actually I just needed how to set the properties of the ClassFactory object so I could reuse my custom itemRenderer in multiple columns. But still, you’ve just opened up my world. Thanks!
Great Article.
I’m new to Flex3 and I have a question for you and I hope u own the answer ..
I want to add a datagrid as ItemRenderer to another main Datagrid ( in other words, nested datagrids).
how should the ItemRenderer Class looks and how to set the dataprovider to the of the sub-Datagrid .
Thanks in advance ..
Hi Mohammad,
While this is technically possible I would strongly recommend against it. You will likely run into performance issues very quickly and I also don’t think it would make for a good user experience to try and decipher nested grids. Perhaps look into the AdvancedDataGrid which allows you to selectively expand and collapse rows.
HTH,
Ben
Thanks Ben
But for my case I have to use the nested datagrid; there are some design issues block me for making a use of the advancedatagrid .
if you can tell me how should the ItemRenderer Class looks like, this will be great and appreciated.
May thanks in advance.
I’ve done it.
Thanks anyway
The one thing I need that I am not sure this will do for me, but perhaps you might help, is to know what datagrid and row I was called from, I have an itemrenderer that is used in a tabbed form with multiple datagrids. In certain cases when the user clicks a check box I need to do different things based on which datagrid they are in.
Any ideas?
Hi Paul,
If you implement ILIstItemRenderer you will have a listData property that gets automatically set for you. When inside a DataGrid it will be an instance of DataGridListData which contains properties like rowIndex, columnIndex and owner (the DG). Thats all from memory so exact names might be a bit off.
HTH,
Ben
Pardon my ignorance, but I am not sure what you mean when you say “If you implement ILIstItemRenderer ” If that is what you get by default when you specify the itemRenderer property on a mx:DataGridColumn, then I have implemented it.
I have breakpointed the code while in a function called from the item renderer, and was unable to find any variables similar to the ones you describe. For fun I tried trace(data.owner) and it returns undefined.
Thanks for the quick response btw.
Hi Paul,
The class you’re using as your renderer needs to implement the interface. So your class definition should look like public class MyRenderer implements IListItemRenderer { …. }
HTH,
Ben
Ah, I am doing it as a component in an existing mxml file, since I won’t need this component outside of this module. Is it possible to do it this way, or do I need to create a separate class?
Hi Ben
Great Article!! It really helped me in solving one of complex problem in my project where I am required to build a dynamic grid with variable no of columns and apply one generic item renderer to apply across all of them.