The Flex DataGrid is an incredibly useful component however there is one feature that is strangely missing in AS3 which is a cellEdit event. One would assume that a listener could be attached to the DataGrid which gets fired when a cell is edited – which is true. You can listen to the itemEndEdit event, unfortunately itemEditEnd is fired before any of the controls or dataProvider are updated. You have access to the original cell value, but the newly entered value is difficult to obtain.
An article by Paul Robertson from Adobe explains the sequence of DataGrid events and provides two workarounds that allow you to get the old and new values when a cell is edited. You can actually put his solution to work right away and get on with your day.
I wasn’t fully satisfied with this approach only because it seems weird that Adobe would make us jump through all these hoops just to get a common bit of data. Paul suggests that that DataProvider would be a good place to listen, but he didn’t find any suitable events. As it turns out, this is exactly the way to do it and I’ll provide example source code below. It actually makes more sense because the DataGrid is just a view and you could have multiple views of this same data throughout your application. When changes occur, they’re handled centrally by the model. This also fits nicely with the current trend of using a model locator singleton and binding UI controls to it. If your UI is bound to the model and the model is listening for changes, the whole thing just works and you don’t really need any code to handle data updates.
The relevant classes in the Flex API are the CollectionEvent.COLLECTION_CHANGE event, which is fired on every change. Updates will contain one or more PropertyChangeEvents which have properties “oldValue,” “newValue” and several other useful properties.
Example Code:
If you don’t want to download the project, the working parts of the code look something like this (note this won’t compile by itself):
<mx:Script> <!--[CDATA[ [Bindable] private var gridData:ArrayCollection = new ArrayCollection(); /** * initialize the data source (fired by creationComplete in WindowedApplication) */ private function onCreationComplete():void { // the listener will detect all changes made to the collection gridData.addEventListener(CollectionEvent.COLLECTION_CHANGE,onCollectionChange); // the source of an ArrayCollection is just an array. we can replace it with // whatever we want . note that this does not cause the change event to fire gridData.source = Widget.getDummyData(); } /** *This is the event handler when any gridData data has been * changed, regardless of what UI Control changed it */ private function onCollectionChange(event:CollectionEvent):void { if (event.kind == CollectionEventKind.UPDATE) { var propChangeEvent:PropertyChangeEvent = event.items[0] as PropertyChangeEvent; var existingItem:Widget = propChangeEvent.source as Widget; // TODO: update the database, web service, etc } else if (event.kind == CollectionEventKind.ADD) { var newItem:Widget = event.items[0] as Widget; // TODO: insert into the database, web service, etc. } } ]]> </mx:Script> <!-- ### Notice there is no listener attached to the grid ### --> <mx:DataGrid id="dg1" editable="true" dataProvider="{gridData}" width="100%" height="100%"> </mx:DataGrid>
Thanks to Paul for getting my gears spinning. I’d appreciate any comments or links to similar example code.
#1 by Jason on September 16th, 2008
One comment, in my source comments I mention that setting the “source” property doesn’t cause the change event to fire. Actually the change event does fire but the kind property is “reset” so it is ignored in this sample code. I’m too lazy to update the zip, but I wanted to clear that up.
#2 by Jason on September 16th, 2008
Another thing to mention is that the change event seems to get thrown multiple times under different circumstances. If your data class is marked as [Bindable] (for example the Widget class) then the PropertyChangeEvent will contain oldValue and newValue. If not, they will both be null. Also the event gets fired twice, the 2nd time oldValue and newValue are null.
One glitch I’ve noticed is that if you sort by a column, then edit the values in that column, you’ll get inconsistent firing of the change event.
#3 by Jesse on November 7th, 2008
“You can listen to the itemEndEdit event, unfortunately itemEditEnd is fired before any of the controls or dataProvider are updated. You have access to the original cell value, but the newly entered value is difficult to obtain.”
I would recommend using itemEndEdit. The newly entered value is not difficult to get.
To get the new value use: event.currentTarget.itemEditorInstance
For example:
// This assumes that you’re using the default
// itemEditor which is a TextInput.
newValue:String = TextInput(event.currentTarget.itemEditorInstance).text
#4 by Jesse on November 7th, 2008
The Flex 3 Cookbook from O’Reilly has a number of good examples on how to use itemEndEdit.
#5 by Jason on November 9th, 2008
Thanks for the comment Jesse – yea, itemEndEdit is what you see explained everywhere. I’m wondering if you have a personal reason for doing it that way? (I really mean that as an honest question, not as a challenge of some sort.)
Adding a listener to the collection has some odd behavior and I still question whether it’s the best way. But the reason I don’t like itemEndEdit is because you have to write a lot of code in the view – particularly in your event handler, you have to write some kind of switch statement to deal with all the columns unless they are all TextInput.
If you have all TextInput fields then its pretty easy but if you have a more typical grid with checkboxes, date fields, dropdowns, maybe some custom item renderers, then you have to know exactly what input control to expect and then cast the correct one in order to get the value. I don’t really like having to handle each column in the grid differently.
If you listen to the collection changes, on the other hand, the oldValue and newValue get passed to your already cast correctly and you don’t have to even know what type of input control was used to trigger the edit. I like dealing with things generically when I can so I don’t have to write a bunch of logic in the view layer.
Basically I think it’s a question of whether you want a smart view & dumb model, or a smart model & a dumb view. I’m taking the approach of a smart model because I’m lazy and I don’t like writing the same event handler code 10 times for the same data just because it appears in several places in the app.
I’d definitely be interested to hear what results people have gotten either way.