itemEditors – Part Two

Editing Events and Complex Editors

In the last article of this series you saw how to make some simple inline itemEditors. If you have read the series on itemRenderers, then you noticed how similar the two are.

The key to making an itemEditor work is a) naming the class using the itemEditor property and b) naming the value property of the itemEditor using the editorDataField property.

In this article I’ll show you how to use events to do some simple data validation and to prevent certain cells from being edited. In the course of this you will see how to make more complex itemEditors.

You can download the source code for this example here.

A word of caution here: by "complex" I do not mean editors with many controls and layouts. I really mean slightly more complex than inline itemEditors. The reason being is that I think it is unfair to ask users to make complex edits within a list or cell of a DataGrid. An editor should be focused only one one thing: the contents of a cell. For example, if you are using the List control and presenting a shopping cart, it is not unreasonable to allow the user to change the quantity of the items in the cart by letting them edit that value right in the cell. What would be unreasonable is to allow them to change the item itself, the colors, quantity, special instructions, and so forth. Or in other words, allow them to shop for items right from the cart when you have a whole site that does that. The cart is just a checkout convenience. Sure, let them add an extra tub of ice cream or delete a bag of chips, but don’t have them turn the bag of chips into a two boxes of whole wheat pasta.

The itemEditEnd event

Let’s say you have a DataGrid which helps you mange inventory. One of things you can do is change part numbers, but you cannot allow a part number to be blank. Using the default itemEditor, the TextInput control, you can click on a cell in the "Part #" column, press the delete key and erase the part number. This is one technique to prevent that.

	<mx:DataGrid x="10" y="64" editable="true" dataProvider="{inventoryDB}"
itemEditEnd="verifyInput(event)">
<mx:columns>
<mx:DataGridColumn headerText="Product" dataField="product"/>
<mx:DataGridColumn headerText="Part #" dataField="part"/>
<mx:DataGridColumn headerText="Type" dataField="type"
itemEditor="editors.ProductTypeEditor" editorDataField="type"/>
<mx:DataGridColumn headerText="Quantity" dataField="quantity"/>
</mx:columns>
</mx:DataGrid>

The list controls dispatch an itemEditEnd event whenever editing is about to be completed. The event happens before the data is commited back to the dataProvider. By handling this event you have the option of changing the data, validating the data, and stopping the commit if necessary. For this example, the verifyInput() function will make sure the product part number is not empty.

		private function verifyInput( event:DataGridEvent ) : void
{
// it is OK if the user cancels the edit
if( event.reason == DataGridEventReason.CANCELLED ) return;

// grab the instance of the itemEditor. For this DataGrid, only the
// TextInput control is used as the editor, so it is safe to get the
// editor no matter what column has been edited.
var editor:TextInput = (event.currentTarget as DataGrid).itemEditorInstance as TextInput;

// if  the edit is on the part number column, make sure it is not blank
if( event.dataField == "part" )
{
if( editor.text.length == 0 ) {
// call event.preventDefault() so the edit will not continue and store the
// blank value
event.preventDefault();
// give the editor an error to display to the user
editor.errorString = "You must enter a part number";
return;
}
}

// handle other columns here
}

The event is a DataGridEvent and contains some very useful properties. The reason property tells you why the event was dispatched. If the user pressed the ESCAPE key or clicked outside of the DataGrid the reason will be DataGridEventReason.CANCELLED. You may want to ignore this event as I have done and just let the DataGrid to its default action which is to cancel the edit and restore the previous value.

If you have decided to handle the event then you will need the itemEditor to get to its properties. The event’s currentTarget property contains the control which I have cast to DataGrid. The DataGrid has an itemEditorInstance property which I cast to TextInput which is the type of itemEditor for this example.

This event handler is called for any cell so you must determine if the edit is something you are interested in pursuing. I check the event’s dataField property to make sure it is the "part" column. If so, I test the editor’s text property to see if there are any characters in it. If there are no characters, two things happen:

First: the event.preventDefault() is called. This is how to prevent the edit from happening – prevent the DataGrid from storing the new value back into the dataProvider.  For the user, they will have pressed TAB or ENTER and nothing will appear to happen. The preventDefault() function will keep the itemEditor in place.

Second: I put an errorString onto the TextInput control. This is optional, but it does signal the user that there is something wrong. Afterall, they pressed the TAB or ENTER key and nothing happened.

The itemEditBeginning Event

There are times you might want to prevent a cell from being edited. You could set the DataGridColumn’s editable property to false, but that prevents every cell from being edited. Suppose you just want to make some of the cells in the column uneditable? You can determine whether a cell is editable or not using the itemEditBeginning event.

    <mx:DataGrid x="10" y="64" editable="true" dataProvider="{inventoryDB}"
itemEditEnd="verifyInput(event)"
itemEditBeginning="allowForEdit(event)">
<mx:columns>
<mx:DataGridColumn headerText="Product" dataField="product"/>
<mx:DataGridColumn headerText="Part #" dataField="part"/>
<mx:DataGridColumn headerText="Type" dataField="type"
itemEditor="editors.ProductTypeEditor" editorDataField="type"/>
<mx:DataGridColumn headerText="Quantity" dataField="quantity"/>
</mx:columns>
</mx:DataGrid>

Handling the itemEditBeginning event gives you the option of dynamically deciding the editability of a cell. In this example, the data has a field called permanent on each record. The idea is that permanent=true means the product name is an unchangable value so the product cell for that row cannot be edited. This is handled by the allowForEdit() function:

        private function allowForEdit(event:DataGridEvent) : void
{
// if the field to be edited is a product, prevent the user from making
// changes if the permanent flag is true<. You can use more complex logic, 
// of course.
if( event.dataField == "product" ) {

var item:Object = ((event.currentTarget as DataGrid).dataProvider as ArrayCollection)[event.rowIndex];
if( item.permanent ) {
event.preventDefault();
}
}

// handle other columns here
}

Again, the event is a DataGridEvent and here I have checked the dataField property of the event to make sure it is the "product" field I am dealing with. I can then get the record from the dataProvider of the DataGrid using the currentTarget property of the event and cast that to DataGrid. I then cast the DataGrid’s dataProvider to ArrayCollection and get the event.rowIndex value. I could also have used the inventoryDB ArrayCollection directly in this function since they are in the same file, but this is more generic.

Once I have the record I can query its permanent property and if it is true, call the event.preventDefault() function to disable editing of that cell. In the case, the default behavior of itemEditBeginning is to present the itemEditor; preventing the default behavior makes the cell uneditable.

Editing Limitations

While I was proof reading this article I thought of something you might try and do and offer a warning. When you are using these edit events and trying to determine if the event should proceed, you may be tempted to make a call to a backend or server process. For example, you may have a web service where you can validate a part number. You may be tempted, while inside of the itemEditEnd event, to make a web service call and validate what the user just entered. Seems logical, right?

Logical maybe, but it won’t work. The reason is that data service calls are asynchronous. You can make the call, sure, but the result will be returned sometime later – well after your event handler has exited. In fact, your call won’t actually be made until your function exits. Your call is queued and when the Flex framework exits the function the request will be made and then the result will be returned by your web service’s result handler.

So there is no way to do this type of server-side validation while editing cells. You should query the server, when your application starts, for the data to validate against, then use that while the cells are being edited.

Conclusion

The ability to dynamically allow editing and to validate the edit is a excellent way to give your users a better experience. You can help them make fewer mistakes and give feedback during the editing process. You can prevent them from editing certain data and make it easier for yourself to write the application since you do not have to validate what the user cannot change.

In next article I’ll cover itemRenderers used as itemEditors.