Yesterday I wasted an hour or so debugging a Flex itemRenderer that I was using to display an image instead of a value in a DataGrid column. The renderer had to simply pick one of six images based on the column value, and so it contained a single <mx:Image> tag and a function that set the Image source dynamically. And then I called that function on the renderer's creationComplete event.
Simple enough. Except that the wrong images were sometimes being displayed, the column had the right data, but the code used to select the image seemed to sometimes pick the wrong image. And what it picked seem to change each time I scrolled the DataGrid up and down!
I actually ran into a very similar issue with a TileList renderer a few weeks ago, but then I had no time to figure out the cause, and so I hacked a workaround. But this time, having been bitten by the same issue twice, I had to find out what was going on.
And what I discovered (by using traces and alerts) is that the creationComplete event does not get fired as I had expected. Rather, it seemed to fire only occasionally, and not once per DataGrid row, and so my image selection function was not being executed as expected.
Once I had figured out the problem I searched the docs for any info on renderers and creationComplete, and found this page. And sure enough, "Flex might reuse an instance of the item renderer or item editor, a reused instance of an item renderer or item editor does not redispatch the creationComplete event". Well, that explained it.
The right way to do what I wanted is to trap the dataChange event instead of creationComplete, as "Flex dispatches the dataChange event every time the data property changes".
And so I am posting this for my own future reference, just to make sure I don't run into it a third time.
I went through the same thing when I first picked up Flex 2...once I thought about it, the idea of recycling item renderers made total sense from a performance standpoint.
Instead of catching dataChange and going through event mechanics, I've often found it easier to deal with this issue by updating the setter for the data property that's on most visual components (I think it's the implementation of IListDataRenderer?):
override public function set data(value:Object):void
{
super.data = value;
// do my stuff
switch (value)
{
case "foo":
myImage.source = "foo.png";
break;
}
}
I'll often go further, adding a bindable private property that is of the type of data being set in, allowing components in the renderer to bind cleanly to it.
dispatchEvent(new FlexEvent(FlexEvent.DATA_CHANGE));
--- Ben
--- Ben
Every itemRenderer (inline or custom component) has a "data" property linked to the its parent's dataProvider. So, you can "bind" it to dynamically transfer the corrent row info to the itemRenderer on Flex's display framework.
Samples (with sources):
- inline:
http://www.vpmjr.com.br/downloads/benforta/itemRen...
- external component:
http://www.vpmjr.com.br/downloads/benforta/itemRen...
It is at this point I am stuck. The datagrid needs to know the popup updated the data. I am not sure how to communicate this. Because of the layers of components I am having trouble communicating back to the datagrid. I tried adding dataChange to the datagrid, but it did not recognize the updated data.
I can email you the test code as it uses AdventureWorks database if you'd like to see it.
Thanks,
Mary
@Vicente - Binding does do the job, but by binding to just "data" you lose compile-time checking. Great for fast and loose work, or where you don't know what type of data you'll be getting, but a common "best practice" is to set to a known type within the renderer.
@Mary - It sounds like the "save" functionality in the popup needs to update the data (AS3 objects, XML, etc.) to which your datagrid is bound in addition to sending it to the server.
Could I possible send you my code to take a look at?
When the item renderer is also a value editor, you'll need to set "rendererIsEditor='true'" and set what field on the component modifies the "dataField" property using column's "editorDataField" property.
So, in this case, you can make use of custom events too to make the opened window dispatch an event that holds in its properties the new value, so you can listen to it inside your component and set the new value to the property at the component you set up to be the "editorDataField".
Without looking to what you're doing, it's that I can tell you.
I didn't practice it yet (I'll do some labs...) but I believe using getter/setter in the component to interface the access to the "data" property, would workaround it. So I just change the bind inside the component to it.
<mx:Script><![CDATA[
import model.Contact;
[Bindable]
private var contact:Contact;
override public function set data(value:Object):void
{
super.data = value;
this.contact = value as Contact;
}
]]></mx:Script>
<!-- components may now bind to this.contact and use compile-time checking, and you get code hinting -->
<mx:Label text="{this.contact.name}" />
When the data prop or listData prop is changed on an itemRenderer it should invalidate its properties by invoking invalidateProperties(), which in turn will invoke commitProperties where you would commit the properties that had changed (i.e. assign the data to you Image).
Firstly we'd check to see if the data or listData were different (i.e. the value hadn't just been set again) before invalidating the properties.
ItemRenderers are re-used in Flex to keep the number of instances created down (i.e. you create enough to display on the screen and re-use them).
http://blogs.adobe.com/aharui/2007/03/thinking_abo...
It's a phenomenal set of entries and his tips are still 100% relevant even with Flex 3 out the door.
Really nice tips!
will try to keep it in my mind
thanks!
Oh well, not anymore!
Kevin