How to: determining when data is edited using the Flash CS3 DataGrid component
Note: a version of this article was posted on the Adobe Developer Center in November 2007. That article is based heavily on this one — essentially I took the text here, and revised a few sentences and word choices for clarity. However, if you’re interested in the “latest” version, you can find it here: “Detecting when data is edited in the DataGrid component.”
Suppose you’re creating a user interface with Flash CS3, using the new lightweight ActionScript 3.0 components. You’re using the DataGrid component to display — what else — a table of data. You want users to be able to edit the data, and you want to know when the user changes the data so that you can update the application.
For example, suppose you’re building something like this simple spreadsheet application:
Naturally, when the user edits a value in the “quantity” column, you’d expect the “Total” value to automatically update.
This sounds like something that should be built in to the DataGrid component, doesn’t it? Alas when I started down the road of trying to figure out how to make it work, while building the Filter Workbench example for Programming ActionScript 3.0, I discovered that it’s not quite as straightforward as I had hoped.
In this article I’ll start with some discussion of what I tried that didn’t work, then follow up with two different approaches that do work (each of which has tradeoffs):
- What doesn’t work — using DataProvider events
-
What does work (with help) — using DataGrid events
- Using two different event listener functions (less convoluted)
- Using one event listener function (more convoluted)
What doesn’t work — using the DataProvider’s events
I started digging through the documentation, and I noticed that the fl.data.DataProvider class (which I was using as the data source for my DataGrid) has some interesting looking events, dataChange and preDataChange. “Ah-ha!” I thought. I can just add a listener for those events, and when the DataGrid changes the underlying data in the DataProvider, it will dispatch those events and (if I need it, which I didn’t) I can grab a snapshot of the old data in preDataChange and get the new data in dataChange. (In fact, in my case I didn’t even need to know which data had changed — it was sufficient for my purposes to know that some data had changed, and then I just recalculated filter values using all the data in the DataGrid.)
Unfortunately, that’s not how the DataProvider works. In fact, its events are designed to handle the opposite use case — i.e. if something other than the DataGrid changes the data in the DataProvider, then the DataProvider notifies the DataGrid so that it can update itself, but if the DataGrid changes the DataProvider, the DataProvider assumes that the DataGrid already knows about the change (and nobody else would want to) so it doesn’t dispatch its events in that case.
So, strike 1 for trying to use the DataProvider’s events.
What does work (with help) — using the DataGrid’s events
Since the DataProvider was out, I had no choice but to look at the DataGrid’s events.
The DataGrid exposes one event, itemEditEnd, that looks like it might be useful. However, there are a couple of problems:
- The
itemEditEndevent is dispatched every time cell editing ends — not just when the cell’s data changes. So the fact that the event is dispatched doesn’t automatically tell us that the data has changed. - Unlike the DataGrid’s
itemEditBeginevent, which has a correspondingitemEditBeginningevent, theitemEditEndevent doesn’t have a corresponding event that you can use to get the value before it is edited (in factitemEditEnd, in most cases, gives you the value before it is changed, whether or not it actually changes). - The event object dispatched by the
itemEditEndevent is a fl.events.DataGridEvent. That class doesn’t have any property such asdataChangedoroldValueandnewValue, so I didn’t see any obvious way to identify whether the data had changed. (The DataGridEvent class does have some properties that end up being useful — but nothing that can be used without some work.)
Eventually, however, after stepping through the source code of the DataGrid several times as it processed editing events, I was able to figure out a couple of ways that you can identify when an edited item’s value has changed in an editable DataGrid.
The premise behind both of these approaches is this: Normally when you register a listener for the DataGrid’s itemEditEnd event, the underlying data in the DataProvider still contains the old values, and hasn’t been updated by the DataGrid. In fact, the DataGrid itself updates the DataProvider by registering for its own itemEditEnd event, then checking whether the original value is different than the new value, and updating the DataProvider it that’s the case. The question is, if the DataGrid is updating the underlying data as part of handling the itemEditEnd event, how can we get the value both before and after it changes, so we can compare them and see if it’s changed?
Using two event listener functions
This is the approach I recommend, although it does require more code (because you have to write two event listener functions). The trick behind this approach is the concept of event priority.
As I mentioned above, the DataGrid itself uses its own itemEditEnd event to update its DataProvider. If you consider the conceptual model of how events work in ActionScript 3.0, basically when a user ends the item editing process (probably by clicking or tabbing away from a particular cell), the DataGrid loops through the list of objects that have registered as listeners for the itemEditEnd event, calling each function in turn. Since the DataGrid uses itemEditEnd to update its DataProvider, that could theoretically lead to inconsistent results. Depending on whether your particular listener function is called before or after the DataGrid’s internal itemEditEnd listener function, the value in the DataProvider may or may not be updated with the new value.
In order to prevent this potential problem, the DataGrid uses event priority to specify that it should be called after most of its event listeners — which is the reason why (in the normal case) the DataProvider contains the pre-edit data rather than the new value (if any). Specifically, when the DataGrid registers as a listener of its own itemEditEnd event, it does so using a priority of -50. Here is the actual code, from line 679 of the DataGrid class’s source code:
addEventListener(DataGridEvent.ITEM_EDIT_END, itemEditorItemEditEndHandler, false, -50);
When you register an event listener in ActionScript, you can optionally specify a priority for your listener (the default, which most listeners use, is 0). Listeners with a higher priority get called first, and listeners with a lower priority get called later. Since the default priority, that’s rarely changed, is 0, any listeners that are registered using the default will get called before the DataGrid’s internal itemEditEnd listener — so they’ll get access to the pre-change value.
However, there’s no reason that your event listener can’t register with a different priority. For that matter, you can register two different listener functions for the same event, with different priorities, which is exactly how this approach works.
You register two functions as listeners for the DataGrid’s itemEditEnd event. The first one should be called before the DataGrid updates the DataProvider, so it must be registered with a priority greater than -50 (I use 100 here):
// This variable will store a temporary value
// to check if the DataGrid's edited item changes
var tempValue:Number;
// register itemEditEnd listener that will be called before an item's value is updated
myGrid.addEventListener(DataGridEvent.ITEM_EDIT_END, itemEditPreEnd, false, 100);
function itemEditPreEnd(event:DataGridEvent):void
{
// get a reference to the datagrid
var grid:DataGrid = event.target as DataGrid;
// get a reference to the name of the property in the
// underlying object corresponding to the cell that's being edited
var field:String = event.dataField;
// get a reference to the row number (the index in the
// dataprovider of the row that's being edited
var row:Number = Number(event.rowIndex);
if (grid != null)
{
// gets the value (pre-edit) from the grid's dataprovider
tempValue = grid.dataProvider.getItemAt(row)[field];
// you could also use this line to get the value
// directly from the cellrenderer that's showing the value
// in the datagrid -- it's the same value.
// That way you wouldn't need a reference to the DataGrid.
//tempValue = event.itemRenderer.data[field];
}
}
The second listener should be called after the DataGrid updates its DataProvider, so it must be registered with a priority less than -50 (I use -100 here):
// register itemEditEnd listener that will be called after an item's value is updated
myGrid.addEventListener(DataGridEvent.ITEM_EDIT_END, itemEditPostEnd, false, -100);
function itemEditPostEnd(event:DataGridEvent):void
{
var grid:DataGrid = event.target as DataGrid;
var field:String = event.dataField;
var row:Number = Number(event.rowIndex);
if (grid != null)
{
// gets the value (post-edit) from the grid's dataprovider
var newValue:Number = grid.dataProvider.getItemAt(row)[field];
// you could also use this line to get the value
// directly from the cellrenderer that's showing the value
// in the datagrid -- it's the same value.
// That way you wouldn't need a reference to the DataGrid.
//var newValue = event.itemRenderer.data[field];
// check if the value has changed
if (newValue != tempValue)
{
// do actions that should happen when the data changes
}
}
}
Notice that in the first listener function (itemEditPreEnd) the function stores the current value in a persistent variable (a variable which is declared outside the function scope). In the second listener function (itemEditPostEnd) that stored value is compared to the final value, and if they’re different we know that the value of the edited item actually changed.
Here’s a working example of this approach:
Using one event listener function
Although I recommend the other approach, if you don’t like the idea of using two listeners for some reason, or if you’re concerned about using the semi-hack of using two different priorities, there is an alternative approach that uses only one event listener.
In this approach, you create a single listener function, and register it with a priority greater than -50 (the default 0 is fine). I repeat, your listener must be triggered before the DataGrid updates the DataProvider, or else you’ll get a runtime error.
This approach works by pulling values from two different places. For the pre-change value, it looks in the DataProvider (or optionally in the cellrenderer for the cell, as shown above). For the post-change value, it actually looks at the DataGrid’s itemEditorInstance property, which is the actual display object that’s used for the editing field, and pulls the raw data from there (basically, it reads the text property of the TextField that’s used in the default item editor). Where it gets complicated is that it has to dynamically determine the name of the property that’s used by the item editor. That way, if you create a DataGrid that uses a custom item editor (e.g. if you have numeric data in a column and use a NumericStepper whenever that column is being edited, rather than the default text field) then you’ll need to know the name of that item editor’s property from which you should get the edited value.
Here’s what the code looks like:
// register itemEditEnd listener to determine when an item is changed
myGrid.addEventListener(DataGridEvent.ITEM_EDIT_END, itemEditEndHandler);
function itemEditEndHandler(event:DataGridEvent):void
{
// get a reference to the datagrid
var grid:DataGrid = event.target as DataGrid;
// get a reference to the name of the property in the
// underlying object corresponding to the cell that's being edited
var field:String = event.dataField;
// get a reference to the row number (the index in the
// dataprovider of the row that's being edited
var row:Number = Number(event.rowIndex);
// get a reference to the column number of
// the cell that's being edited
var col:int = event.columnIndex;
if (grid != null)
{
// gets the value (pre-edit) from the grid's dataprovider
var oldValue:Number = Number(grid.dataProvider.getItemAt(row)[field]);
// you could also use this line to get the value
// directly from the cellrenderer that's showing the value
// in the datagrid -- it's the same value.
// That way you wouldn't need a reference to the DataGrid.
//var oldValue = event.itemRenderer.data[field];
// get the value (post-edit) from the item editor
var newValue:Number = Number(grid.itemEditorInstance[grid.columns[col].editorDataField]);
// check if the value has changed
if (newValue != oldValue)
{
// do actions that should happen when the data changes
// Note that in this case, the dataprovider
// hasn't been updated yet, so you can't do any
// actions that require the dataprovider to have
// the new data
}
}
}
In fact, if you already know for certain what type of object is being used as the item editor, you don’t need to use such convoluted code. For example, with the default item editor, grid.columns[col].editorDataField will always be “text” (the .text property) so you can just use code like this to get the post-edit value:
var newValue:Number = Number(grid.itemEditorInstance.text);
And of course, if you’re using some custom item editor, you can just hard-code the appropriate property name. For example, if you’re using a NumericStepper instead of the default item editor, you can use “value” instead of “text”:
var newValue:Number = grid.itemEditorInstance.value;
Note the biggest drawback to this approach, as explained in the comments: the DataProvider hasn’t been updated by the DataGrid yet, so if you want to do anything using the DataProvider this approach won’t work (unless you use some technique to delay your actions until after the DataGrid has updated its DataProvider). As stated previously, you can’t use this technique after the DataGrid has updated the DataProvider; if you try to, you will not be able to get the pre-edit value (because the DataProvider will have already changed) and you’ll get a runtime exception (because the item editor will have already been destroyed, so you won’t be able to access it anymore to get the new value).
Here’s a working example of this approach (it looks the same as the previous one, of course):
Extra
Download the examples from this article (1.1MB .zip — as usual, a FLA with components is BIG).
You can leave a comment, or trackback from your own site.


Pingback: links for 2008-06-13 « M@’s Blog
Pingback: VerySimple Dev Blog
Pingback: dataprovider and datachange event - ActionScript.org Forums