How to: determining when data is edited using the Flash CS3 DataGrid component

Posted May 1, 2007 2:06 pm
Filed under: AS3, ActionScript, Articles by Paul, Flash, Tutorials

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 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:

  1. The itemEditEnd event 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.
  2. Unlike the DataGrid’s itemEditBegin event, which has a corresponding itemEditBeginning event, the itemEditEnd event doesn’t have a corresponding event that you can use to get the value before it is edited (in fact itemEditEnd, in most cases, gives you the value before it is changed, whether or not it actually changes).
  3. The event object dispatched by the itemEditEnd event is a fl.events.DataGridEvent. That class doesn’t have any property such as dataChanged or oldValue and newValue, 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.

19 Comments so far


  1. Monkeeh is reported to have said:

    thanks for great how-to! this one helps me alot!


  2. Nick is reported to have said:

    Hi,

    This is superb — exactly what I needed and a very clear & concise explanation. Ridiculous that this functionality isn’t built-in, isn’t it?

    Cheers Paul!

    -Nick


  3. BuffDogg is reported to have said:

    *** IF USING A CUSTOM ITEM EDITOR ***

    I built a DataGrid that uses a “check box” as a custom item editor. Due to the nature of the CheckBox, as soon as you click on the cell you change the value (check or uncheck). When this happens you are never able to retrieve the “old value” within the ITEM_EDIT_END event. This function always contains the edited value (new value).

    A single listener approach will not work for this. However the 2 listener approach works fine with a simple modification. Simply change the first event listener function (itemEditPreEnd) to fire on the ITEM_EDIT_BEGINNING event instead of the ITEM_EDIT_END event. The standard cell editor still functions correctly as well as the custom cell editor.

    I would recommend the 2 listener approach with the first listener firing on the ITEM_EDIT_BEGINNING event.

    Paul, if you’re interested I can send you some example files.


  4. Check PageRank is reported to have said:

    Thank you for the useful informations…


  5. Buy photo is reported to have said:

    Thanks for this very good article ..


  6. Vivek Lakhanpal is reported to have said:

    Thanks Paul for such a helpful information.

    Can you please tell me or write one such article on how to add icon/image/movieclip to DataGrid component using AS3.0?

    Thanks in Advance.

    Vivek


  7. Paul is reported to have said:

    @Vivek:

    It sounds like you want to use a custom cell renderer to display an image rather than text in a DataGrid cell. Here’s a “quick start” article on the data grid that my co-worker Peter deHaan wrote for the Adobe Developer Center, which I think will answer your question:
    http://www.adobe.com/devnet/flash/quickstart/datagrid_pt3/

    At the end of the article it includes a section on creating a custom cell renderer to display an image in a cell (and it could be easily changed to use a movie clip, I think).

    It’s actually the third of three articles he did on the data grid. (Part 1 Part 2 Part 3) I highly recommend them for anyone who’s trying to figure out how to work with the data grid and customize it.


  8. Vivek Lakhanpal is reported to have said:

    Hi Paul,

    Thanks for replying back to my previous query personally.

    It was so nice of you.

    I have moved to next stage of DataGrid implementation and encountered another strange problem. Please help me in this regard if you can.

    The problem is:

    I am using Data Grid to display buttons having up and down stages in a single column. These buttons are being placed dynamically in the column based on the data available. User can click on a button and change/toggle the state of the button. The problem is the that when we click one button in view area and scroll down the Grid it shows the buttons clicked after a regular interval in all available data even when they are not clicked i.e. only single button is clicked and sometimes when we scroll back to the initial position the button we click no longer remained clicked but some other button show up as clicked.

    Thanks,
    Vivek.


  9. Dragon Digital is reported to have said:

    DID YOU TRY CELLEDIT???..

    I think there’s a better way for triggering the data cell changes… there’s a cellEdit event for the datagrid component and there are examples in the flash help… (actionscript2)

    // Create the listener
    var listenerCellEdit = new Object();
    listenerCellEdit.cellEdit = function(evt_obj) {

    // Retrieve location of cell that was changed.
    var cell_obj:Object = “(“+evt_obj.columnIndex+”, “+evt_obj.itemIndex+”)”;

    // Retrieve cell value that was changed and is currently selected by the mouse and highlited:
    var value_obj:Object = evt_obj.target.selectedItem.Price;

    // Retrieve cell value at the current row index, (means highlited or not) for the column Price… and of course note this is case sensitive
    my_dg.getItemAt(evt_obj.itemIndex).Price;

    my_dg.addEventListener(“cellEdit”,listenerCellEdit);


  10. Paul is reported to have said:

    @Dragon Digital:

    As you say, for the ActionScript 2.0 Flash DataGrid (aka the “V2” Flash component set) that’s probably a good approach.

    However, this article is about working with the ActionScript 3.0 DataGrid that ships with Flash CS3, which doesn’t have a cellEdit event. So unfortunately for people working with the Flash CS3 DataGrid component that solution isn’t an option.


  11. links for 2008-06-13 « M@’s Blog is reported to have said:

    […] How to: determining when data is edited using the Flash CS3 DataGrid component, a bunch of Words, pu… It’s so sad how complicated everything has to be sometimes… and flex/actionscript is no different. Well, it’s different, I like it far better than Dot Net, but it still makes me sad that it’s complicated - it makes me feel like we just can’t get this r (tags: Datagrid actionscript flex) […]


  12. Brian is reported to have said:

    Anyone have the problem that sometimes the underlying collection doesn’t seem to get updated? Sporadically, it seems like the edit doesn’t take hold. I’m not sure if the ITEM_EDIT_END event is thrown or not, but the collection itself is not updated. In fact, if I tab around the DataGrid cells, sometimes the old value seems to appear in a cell I just edited.

    Again, this only happens sporadically…


  13. Riccardo is reported to have said:

    Hi. I’m new to AS3 code and I’m keen on every kind of help I can find. This is great, because I’m actually working with the DataGrid component and I still have to figure out its way of working.
    Thanks a lot.


  14. Tristan is reported to have said:

    No offense to BuffDog up there, but for a boolean checkbox value, if it has changed, you always know the previous value without having to look. It is always (always) simply the opposite of the current value.

    Hope you didn’t spend too long figuring that out…


  15. VerySimple Dev Blog is reported to have said:

    […] article by Paul Robertson explains the sequence of DataGrid events and provides two workarounds that allow you to get the […]


  16. Jason is reported to have said:

    Hey Paul, thanks for this great article - it really got my gear spinning and I learned how the sequence of events fire in the DataGrid.

    I came up with another method of handling DataGrid updates which involves listening to the dataProvider instead. If you want to take a look, I posted an article here: http://www.verysimple.com/blog/2008/09/10/handling-data-updates-with-the-flex-as3-datagrid/


  17. dataprovider and datachange event - ActionScript.org Forums is reported to have said:

    Kramer auto Pingback[…] this article http://probertson.com/articles/2007/…/#dataprovider __________________ —————————— Yogesh Puri […]


  18. Tarun is reported to have said:

    As said I am also facing the same problem that sometimes the underlying collection doesn’t seem to get updated. “It seems like the edit doesn’t take hold. I’m not sure if the ITEM_EDIT_END event is thrown or not, but the collection itself is not updated. In fact, if I tab around the DataGrid cells, sometimes the old value seems to appear in a cell I just edited.”

    Has anyone else also faced the same problem and any workarounds for this?


  19. Terry is reported to have said:

    Hi Everyone,
    As Tarun and Brian said that sometimes the value is not updated, I also sometimes get the same problem. Any Clues???

Add your comment





Comment notes

Please keep comments on topic. Comments that are inappropriate or offensive will be edited or removed.

Paragraphs and line breaks are automatically converted to HTML, and quotation marks are converted to “smart” quotes.

The following XHTML tags can be used: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> . All others will be removed.

Articles by Type

Articles by Topic

Random Reading

Currently...

Subscribe