Datagrid item editors and dynamic behavior (part 2/3)

picking up from where we left off in part one, now we want to to improve our user notification when they enter a bad value in our datagrid. previously, we jumped out of the cell and left it blank. ideally, we want our cell, whose itemEditor is a derived class of TextInput, to behave like a TextInput with bad data. i.e. we would like a red border and an errortip.to get this behavior, we must have an understanding of how DataGrid works with regard to item editors and how to short-circuit its default behavior. by default, once you leave a datagrid cell, the DataGrid destroys the item editor and looks for the next cell to edit. this is why we got the “leave the cell” behavior in our first iteration. when a user leaves a datagrid cell, which dispatches the ITEM_EDIT_END event, the DataGrid invokes its handler method: itemEditorItemEditEndHandler. if the user has not canceled the edit, that method pulls the data out of the item editor (based on the value of editorDataField), puts the value in the cell, and under normal circumstances destroys the itemEditor. what we want to do is call our own handler when we get the ITEM_EDIT_END event. we do this by specifying a value for the itemEditEnd property on our DataGrid, like this: <mx:DataGrid id="weigh_in" editable="true" dataProvider="{wrestlers}" itemEditEnd="processEditedItem(event)" > when our handler is called, we need to decide whether or not the value is good. if it is, we’ll simply exit and let the framework handle the rest (putting the value into the cell, destroying the item editor, and finding the next cell to edit). if the value is bad, however, we need to prevent that default behavior, by calling the preventDefault() method on the DataGridEvent passed to our handler: event.preventDefault(); though the DataGrid’s ITEM_EDIT_END handler is still invoked, this prevents the DataGrid from leaving the cell. however, it still destroys the item editor, so we need to be sure we’re done with the item editor before that happens. a moment ago, i said that we needed to take action if our value is no good. how do we know if it’s good or not? what would be ideal is if our item editor could somehow indicate that to the framework so that our event could tell us (a ‘valid’ flag, perhaps?), but there’s no framework support for that. so i did something rather ugly but necessary: i made the validate() method in our itemEditor public so that it could be invoked from our custom handler. and then i did something even uglier: i overloaded the return of that validation method to return the error string if it’s in error, and a null otherwise. in general, i’m not at all a fan of overloading a return type for such double meaning, but it seemed to be the most straightforward way of getting done what was needed without making multiple changes to the framework to support a more elegant solution. so our approach in the handler is to grab a reference to our itemEditor and invoke its validation method from there. that will give us the information we need to determine if we need to stay in our cell our move on to the next. here is our handler: protected function processEditedItem(event:DataGridEvent) : void { if (event.reason == DataGridEventReason.CANCELLED) return; if (event.dataField != 'weight') return; // we need a reference to our style of item editor so we can call // its validation method. var editor:WeightEditor2 = WeightEditor2(weigh_in.itemEditorInstance); var userEnteredValue:String = editor.text; var errorMsg:String = editor.validate(userEnteredValue); // if we didn't validate, we'll set our errorString and call preventDefault() // so the datagrid knows to keep us in the same cell if (errorMsg) { editor.errorString = errorMsg; event.preventDefault(); } } we want to bug out straightaway if the user has canceled the edit (by hitting escape) or if our handler was called for a column in which we’re not interested. in both cases, the framework will handle the work. otherwise, we’ll grab a reference to our itemEditor, call its validation method with the data the user entered, and if we get back an error string from the validator, we’ll use that as the error tip and call preventDefault. here’s the code: wresting2.mxml <?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" > <mx:Script> <![CDATA[ import mx.events.DataGridEventReason; import mx.events.DataGridEvent; import mx.formatters.NumberFormatter; /** * Our label function. It formats entered weights as proper numbers. * * @param item      The user-entered weight * @param column   The column in which the weight was entered * * @return   A formatted weight */ protected function decimalLabel(item:Object, column:DataGridColumn) : String { var formatter:NumberFormatter = new NumberFormatter(); var weight:String = item.weight; var formattedNumber:String = formatter.format(weight); return formattedNumber; } /** * This is our method for processing ITEM_EDIT_END events on our * weight column. We'll grab weight and validate. * * If the weight is a valid number, we'll let the framework handle the * rest of the process, such as finding the next cell to edit and calling * our label function. * * If the weight is not a valid number, we'll set our errorTip to the error * message we got back from validation, and ensure the framework does not * continue processing so that the user will stay in the same cell and can * enter a new value. * * @param event      Our ITEM_EDIT_END event. */ protected function processEditedItem(event:DataGridEvent) : void { if (event.reason == DataGridEventReason.CANCELLED) return; if (event.dataField != 'weight') return; // we need a reference to our style of item editor so we can call // its validation method. var editor:WeightEditor2 = WeightEditor2(weigh_in.itemEditorInstance); var userEnteredValue:String = editor.text; var errorMsg:String = editor.validate(userEnteredValue); // if we didn't validate, we'll set our errorString and call preventDefault() // so the datagrid knows to keep us in the same cell if (errorMsg) { editor.errorString = errorMsg; event.preventDefault(); } } ]]> </mx:Script> <mx:XMLList id="wrestlers" xmlns=""> <wrestler> <name> Bob Smith </name> <weightClass> A </weightClass> <weight> </weight> </wrestler> <wrestler> <name> Tim McLarry </name> <weightClass> B </weightClass> <weight> </weight> </wrestler> <wrestler> <name> Joe Doug </name> <weightClass> C </weightClass> <weight> </weight> </wrestler> <wrestler> <name> Larry McTim </name> <weightClass> D </weightClass> <weight> </weight> </wrestler> </mx:XMLList> <mx:DataGrid id="weigh_in" editable="true" dataProvider="{wrestlers}" itemEditEnd="processEditedItem(event)" > <mx:columns> <mx:DataGridColumn id="wrestler_name" headerText="Name" editable="false" dataField="name" /> <mx:DataGridColumn id="weight_class" headerText="Class" editable="false" dataField="weightClass" /> <mx:DataGridColumn id="weight" headerText="Weight" editable="true" dataField="weight" itemEditor="WeightEditor2" editorDataField="data" labelFunction="decimalLabel" /> </mx:columns> </mx:DataGrid> <mx:Form > <mx:FormHeading label="Weight Classes" /> <mx:FormItem label="A" > <mx:Label text="0-99 lbs." /> </mx:FormItem> <mx:FormItem label="B" > <mx:Label text="100-135 lbs." /> </mx:FormItem> <mx:FormItem label="C" > <mx:Label text="136-169 lbs." /> </mx:FormItem> <mx:FormItem label="D" > <mx:Label text="170-210 lbs." /> </mx:FormItem> </mx:Form> </mx:Application> WeightEditor2.mxml: <?xml version="1.0" encoding="utf-8"?> <mx:TextInput restrict="1234567890,." xmlns:mx="http://www.adobe.com/2006/mxml"> <mx:Script> <![CDATA[ import mx.formatters.NumberFormatter; import mx.events.ValidationResultEvent; import mx.validators.NumberValidator; /** * This method is invoked when the user enters a value in the weight column * of the weigh_in datagrid. Because we're allowing the user to handle validation, * we'll always return what the user typed in, right or wrong. By sending back a * value which will eventually fail validation, that allows us to keep the value * in the field when the errortip is drawn around it. */ override public function get data() : Object { var value:String = text; // no need to validate here, since we don't act upon it. but if we did // need to validate to make some kind of decision, then we'll end up // validating twice. return value; } /** * This is our validation method. We pass validation if the user has * entered a non-negative decimal. This is a public function so that we can * re-use this validation outside of the editor. * * @param value   The user-entered value * @return null if we pass validation, otherwise we return the error string */ public function validate(value:String) : String { var validator:NumberValidator = new NumberValidator(); validator.allowNegative = false; validator.required = false; var result:ValidationResultEvent = validator.validate(value); if (result.type != ValidationResultEvent.VALID) return result.message; return null; } ]]> </mx:Script> </mx:TextInput> you’ll notice that i introduced a label function on the weight column. that’s there to handle formatting numbers. for example, if the user entered 000122, we’ll display that as 122. for part 3, all we’ve got left is to change our validation so that we’ll flag an error if the entered weight is out-of-range of the weight class. click on the image to run the new app. to see the error behavior, enter a non-number such as 5.5.5. NOTE: there is a bug in flex2 DataGrid so that the behavior of the last cell is incorrect. if you enter a bad value in the last cell of the weight column and tab out, the value will disappear. if you hit enter, though, we’ll stay in the cell as expected. it seems this is fixed in flex 3.

There are no comments.

Leave a Reply