Propagating model changes to a DataGrid

Flex offers a nice way to represent data in a model and configure this model as a data source for a DataGrid or other visual components (ComboBox, AdvancedDataGrid, etc.). This allows for a quick start but sometimes deadends when a change to the model is not propagated to the view. I am trying here to explain a few aspects related to this.

The first model

[Bindable]
private var dataSource1:ArrayCollection = new ArrayCollection([
      {col1:'1A', col2: '1B'},
      {col1:'2A', col2: '2B'}
]);

The DataGrid

        <mx:DataGrid id = "grid" width="100%" height="100%" dataProvider="{dataSource1}">
            <mx:columns>
                <mx:DataGridColumn dataField="col1"/>
                <mx:DataGridColumn dataField="col2"/>
                <mx:DataGridColumn dataField="col3" labelFunction="objLabel"/>
            </mx:columns>
        </mx:DataGrid>

Changing the value of dataSource.getItemAt(0).col1 will not be propagated to the view. Why? Actualy this is simple, all the “magic” of Bindable objects is based on events. When something changes an event is generated with that change and the view is updated. This does not happend for simple {key:val} objects. The solution is to use an ObjectProxy to encapsulate the object. The ObjectProxy contains a map. When the value of a property in the map is modified an event is fired.

The second model

[Bindable]
private var dataSource2:ArrayCollection = new ArrayCollection([
       new ObjectProxy({col1:'1A', col2: '1B'}),
       new ObjectProxy({col1:'2A', col2: '2B'})
]);

Now when the property col1 is updated the view will be automaticaly updated because the object proxy will fire an event. You can find more on the same topic here.

More complicated data

If your model contains more complicated data such an object the ObjectProxy will not be able to detect changes to the objects themselves so another mechanism must be found. Here is a simple object for a cell (read this doc to understand the [Bindable] at the begining):

package{
    [Bindable]
    public class TestCell{
        public var value:String;
        public function TestCell(){}
    }
}

and a function to render it:

            public function objLabel(item:Object, column:DataGridColumn):String{
                if(item[column.dataField] != null){
                    return (item[column.dataField] as TestCell).value;
                }
                return '-';
            }

Now if the value of the value property in the cell is changed nothing will be updated:

            private function modify2(event:Event):void {
                (grid.dataProvider.getItemAt(0).col3 as TestCell).value = newVal.text;
            }

The solution is to fire the event manually:

            private function modify3(event:Event):void {
                (grid.dataProvider.getItemAt(0).col3 as TestCell).value = newVal.text;
                var op:ObjectProxy = grid.dataProvider.getItemAt(0) as ObjectProxy;
                op.dispatchEvent(PropertyChangeEvent.createUpdateEvent(op, 'col3', null, newVal.text));
            }

Here is how to use the following example:

  • type a value, click on edit1, the model changed but not the view
  • select dataSource2 then dataSource1 again to see the change
  • select dataSource2, type a value, click on edit1, the model changes and so does the view
  • select dataSource2, type a value, click on edit2, the cell in the model changes but the view does not
  • select dataSource1 then dataSource2 again to see the change
  • select dataSource2, type a value, click on edit3, the cell in the model changes and so does the view

[flash http://www.len.ro/hidden/flex/testBinding/TestBinding.swf w=580 h=320]

Complete sources follow:

TestBinding.as:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"
	creationComplete="initCell()">
	<mx:Script>
		<![CDATA[
			import mx.controls.dataGridClasses.DataGridColumn;
			import mx.controls.advancedDataGridClasses.AdvancedDataGridColumn;
			import mx.events.PropertyChangeEvent;
			import com.mccsoft.diapason.budget.BudgetScreenCell;
			import mx.utils.ObjectProxy;
			import mx.collections.ArrayCollection;

			[Bindable]
			private var dataSource1:ArrayCollection = new ArrayCollection([
				{col1:'1A', col2: '1B'},
				{col1:'2A', col2: '2B'}
				]);

			private var dataSource2:ArrayCollection = new ArrayCollection([
				new ObjectProxy({col1:'1A', col2: '1B'}),
				new ObjectProxy({col1:'2A', col2: '2B'})
				]);

			private var cell:TestCell;

			private function initCell():void {
				cell = new TestCell();
				cell.value = 'cell value';
				dataSource1.getItemAt(0)['col3'] = cell;
				dataSource2.getItemAt(0)['col3'] = cell;
			}

			public function objLabel(item:Object, column:DataGridColumn):String{
				if(item[column.dataField] != null){
					return (item[column.dataField] as TestCell).value;
				}
				return '-';
			}

			private function addRow(event:Event):void {
				grid.dataProvider.addItem({col1:'xA', col2: 'xB'});
			}

			private function modify1(event:Event):void {
				grid.dataProvider.getItemAt(0).col1 = newVal.text;
			}

			private function modify2(event:Event):void {
				(grid.dataProvider.getItemAt(0).col3 as TestCell).value = newVal.text;
			}

			private function modify3(event:Event):void {
				(grid.dataProvider.getItemAt(0).col3 as TestCell).value = newVal.text;
				var op:ObjectProxy = grid.dataProvider.getItemAt(0) as ObjectProxy;
				op.dispatchEvent(PropertyChangeEvent.createUpdateEvent(op, 'col3', null, null));
			}
		]]>
	</mx:Script>
	<mx:VBox width="100%" height="100%">
		<mx:DataGrid id = "grid" width="100%" height="100%" dataProvider="{dataSource1}">
			<mx:columns>
				<mx:DataGridColumn dataField="col1"/>
				<mx:DataGridColumn dataField="col2"/>
				<mx:DataGridColumn dataField="col3" labelFunction="objLabel"/>
			</mx:columns>
		</mx:DataGrid>
		<mx:HBox>
			<mx:Label text="select source"/>
			<mx:ComboBox id="ds" change="{grid.dataProvider = this[ds.selectedItem]}">
				<mx:ArrayCollection>
					<mx:String>dataSource1</mx:String>
					<mx:String>dataSource2</mx:String>
				</mx:ArrayCollection>
			</mx:ComboBox>
			<mx:Button label="Add row" click="addRow(event)"/>
		</mx:HBox>
		<mx:HBox>
			<mx:Label text="Value"/>
			<mx:TextInput id="newVal" width="50"/>
			<mx:Button label="Edit 1" click="modify1(event)" toolTip="Change value in row1/col1"/>
			<mx:Button label="Edit 2" click="modify2(event)" toolTip="Change value in row1/col3 without event"/>
			<mx:Button label="Edit 3" click="modify3(event)" toolTip="Change value in row1/col3 with event"/>
		</mx:HBox>
	</mx:VBox>
</mx:Application>

4 Responses

  1. Listing of TestCell class is missing.

    Interested to compile your sample, seems to be very useful for me to get a better understanding of how ObjectProxy class can be used in Flex applications.

  2. The TestCell class listing is the first code section in the “More complicated data” part.

  3. How that the spark components are out, is this method still applicable or is there a better way when using spark components?

  4. I am sorry Chris, I am stuck on flex 3.5 project so we did not migrated and did not had to test the with Spark components.

Leave a Reply

*