2 Replies Latest reply on May 17, 2011 1:18 PM by Justin Daily

    Setting cell values in DataGrid

    Justin Daily Level 1

      I have an application with a custom component called DataEntryDataGrid (which is a subclass of mx:DataGrid) that I based on this blog post:  http://blogs.adobe.com/aharui/2008/03/custom_arraycollections_adding.html

       

      The component works great, but in this particular datagrid I need some special functionality.   After the first row of data is entered and the user tabs into the next row, I need the first and second columns to be filled in based on the values of the previous row, and then I need it to automatically focus on the third column's cell.  While the first and second columns should be still editable, they will be largely repetitive, and it would help if the users didn't have to enter the same numbers again and again.  The first column in a new row should be the same value as the first column in the last row, and the second column in a new row should be (last row's value +1). Example:

       

      DataGrid:
      
      | Slide No. | Specimen No. | Age | Weight | Length |
      |    1      |     1        |  5  |  65    |  40    |  <- This row is manually entered, just text inputs
      |    1*     |     2*       |  #  |        |        |
      
      * = values set programatically, these cells should still be focusable and editable
      # = this is where the focus should be
      

       

      The problem I'm having is that when I tab into the next row, the first column value doesn't get set.  The second column gets set to the correct value and displayed correctly, and the focus is set to the correct cell (the third column), but the first column remains empty.  I'm not sure why this is.  If I set a breakpoint in the code during the function focusNewRow()  (which is called at the dataGrid's "itemFocusIn" event)  the value of "slideNo" (first column) is set to the correct value, but after the "focusNewRow" functions finishes, a trace of dataProvider[the current row].slideNo shows the value is blank.  Not null, just blank.  Traces of all other columns show the correct values.  Anyone have any ideas?  Here's the code for my main application:

       

      <?xml version="1.0" encoding="utf-8"?>
      <s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 
               xmlns:s="library://ns.adobe.com/flex/spark" 
               xmlns:mx="library://ns.adobe.com/flex/mx" minWidth="955" minHeight="600" xmlns:components="components.*">
        <fx:Script>
          <![CDATA[
            import mx.controls.DataGrid;
            import mx.events.DataGridEvent;
            
            public function traceSlideNo():void {
              var i:int;
              var g:Object = myDataGrid.dataProvider;
              for(i = 0; i < g.length -1; i++) {
                trace("sl: " + g[i].slideNo + ", sp: " + g[i].specimenNo + ", age: " + g[i].age);
              }
            }
            
            public function focusNewRow(e:DataGridEvent):void {
              if(e.currentTarget.dataProvider.length > 0 && e.rowIndex != 0 && e.columnIndex == 0) {
                var dg:DataGrid = e.currentTarget as DataGrid;
                var lastItem:Object = dg.dataProvider[e.rowIndex - 1];
                var targetItem:Object = dg.dataProvider[e.rowIndex];
                if(targetItem.specimenNo == "") {
                  var focusCell:Object = new Object();
                  focusCell.rowIndex = e.rowIndex;
                  focusCell.columnIndex = 2;
                  dg.editedItemPosition = focusCell;
                  
                  targetItem.slideNo = int(lastItem.slideNo);
                  targetItem.specimenNo = int(lastItem.specimenNo) + 1;
                  
                  callLater(dg.dataProvider.refresh);
                }    
              }
            }
          ]]>
        </fx:Script>
        
        <components:DataEntryDataGrid x="10" y="10" width="450" id="myDataGrid" itemFocusIn="focusNewRow(event)"
                        editable="true" rowHeight="25" variableRowHeight="false">
          <components:columns>
            <mx:DataGridColumn headerText="Slide No." dataField="slideNo" editable="true"/>
            <mx:DataGridColumn headerText="Specimen No." dataField="specimenNo" editable="true"/>
            <mx:DataGridColumn headerText="Age" dataField="age" editable="true"/>
            <mx:DataGridColumn headerText="Weight" dataField="weight" editable="true"/>
            <mx:DataGridColumn headerText="Length" dataField="length" editable="true"/>
          </components:columns>
        </components:DataEntryDataGrid>
        <s:Button x="10" y="195" label="Trace Slide Numbers" click="traceSlideNo()"/>
      </s:Application>
      

       

      And here's the custom component, DataEntryDataGrid, just for reference (placed in the "components" package in this example) :

       

      <?xml version="1.0" encoding="utf-8"?>
      <mx:DataGrid xmlns:fx="http://ns.adobe.com/mxml/2009" 
             xmlns:s="library://ns.adobe.com/flex/spark" 
             xmlns:mx="library://ns.adobe.com/flex/mx" initialize="init(event)"
             editable="true" wordWrap="true" variableRowHeight="true">
        <fx:Declarations>
          <!-- Place non-visual elements (e.g., services, value objects) here -->
        </fx:Declarations>
        
        
        
        <fx:Script>
          <![CDATA[
            import components.NewEntryArrayCollection;
            
            import mx.controls.Alert;
            import mx.controls.dataGridClasses.DataGridColumn;
            import mx.events.DataGridEvent;
            import mx.events.DataGridEventReason;
            import mx.events.FlexEvent;
            import mx.utils.ObjectUtil;
            
            private var arr:Array = [];
            private var ac:NewEntryArrayCollection;
            private var dg:DataGrid;
            
            public var enableDeleteColumn:Boolean;
            
            private function generateObject():Object
            {
              // Returns a new object to the datagrid with blank entries for all columns
              var obj:Object = new Object();
              for each(var item:Object in this.columns) {
                var df:String = item.dataField.toString();
                obj[df] = "";
              }
              return obj;
            }
            
            private function isObjectEmpty(obj:Object):Boolean
            {
              // Checks to see if the current row is empty
              var hits:int = 0;
              
              for each(var item:Object in this.columns) {
                var df:String = item.dataField.toString();
                if(obj[df] != "" || obj[df] !== null) {
                  hits++;
                }
              }
              if(hits > 0) {
                return false;
              }
              return true;
            }      
            
            private function init(event:FlexEvent):void
            {
              dg = this;                // Reference to the DataEntryDataGrid
              ac = new NewEntryArrayCollection(arr);  // DataProvider for this DataEntryDataGrid
              ac.factoryFunction = generateObject;
              ac.emptyTestFunction = isObjectEmpty;        
              dg.dataProvider = ac;
              
              // Renderer for the DELETE column and Delete Button Item Renderer
              if(enableDeleteColumn == true){
                var cols:Array = dg.columns;
                var delColumn:DataGridColumn = new DataGridColumn("del");
                delColumn.editable = false;
                delColumn.width = 35;
                delColumn.headerText = "DEL";
                delColumn.dataField = "delete";
                delColumn.itemRenderer = new ClassFactory(DeleteButton);
                cols.push(delColumn);
                dg.columns = cols;
                dg.addEventListener("deleteRow",deleteClickAccept);
              }
            }
            
            private function deleteClickAccept(event:Event):void { // Handles deletion of rows based on event dispatched from DeleteButton.mxml
              dg = this;
              ac = dg.dataProvider as NewEntryArrayCollection;
              if(dg.selectedIndex != ac.length - 1) {
                ac.removeItemAt(dg.selectedIndex);
                ac.refresh();
              }
            }
      
          ]]>
        </fx:Script>
        
      </mx:DataGrid>
      

       

      Also, the file NewEntryArrayCollection.as which is referenced by the custom component.  This also goes in the "components" package:

       

      package components 
      {
        import mx.collections.ArrayCollection;
        
        public class NewEntryArrayCollection extends ArrayCollection
        {
          private var newEntry:Object;
          
          // callback to generate a new entry
          public var factoryFunction:Function;
          
          // callback to test if an entry is empty and should be deleted
          public var emptyTestFunction:Function;
          
          public function NewEntryArrayCollection(source:Array)
          {
            super(source);
          }
          
          override public function getItemAt(index:int, prefetch:int=0):Object
          {
            if (index < 0 || index >= length)
              throw new RangeError("invalid index", index);
            
            if (index < super.length)
              return super.getItemAt(index, prefetch);
            
            if (!newEntry)
              newEntry = factoryFunction();
            return newEntry;
          }
          
          override public function get length():int
          {
            return super.length + 1;
          }
          
          override public function itemUpdated(item:Object, property:Object = null, 
                             oldValue:Object = null, 
                             newValue:Object = null):void
          {
            super.itemUpdated(item, property, oldValue, newValue);
            if (item != newEntry)
            {
              if (emptyTestFunction != null)
              {
                if (emptyTestFunction(item))
                {
                  removeItemAt(getItemIndex(item));
                }
              }
            }
            else
            {
              if (emptyTestFunction != null)
              {
                if (!emptyTestFunction(item))
                {
                  newEntry = null;
                  addItemAt(item, length - 1);
                }
              }
            }
          }
          
        }
      
      }
      

       

      Sorry for the length of this post, but I hate seeing people post without including enough information to solve the problem.  If there's anything I've left out, let me know.

        • 1. Re: Setting cell values in DataGrid
          Flex harUI Adobe Employee

          I think in the NewEntryArrayCollection, I would wire it up to generate a

          populated item instead of doing it where you are right now.

          • 2. Re: Setting cell values in DataGrid
            Justin Daily Level 1

            Problem solved.  Actually, the NewEntryArrayCollection pointed to an outside function within the DataEntryDataGrid component to be used as a factory function for new objects.  I just set the factory function to scan the previous row's values and base the new row's values off of them.  Thanks again, Flex!

             

            New private function generateObject() to replace the previous one in DataEntryDataGrid.mxml, just in case others are curious:

            private function generateObject():Object
            {
              // Returns a new object to the datagrid with filled in slide and 
              // specimen no. columns and the rest of the columns blank
              
              var obj:Object = new Object();
              var thisDP:Object;
              for each(var item:Object in this.columns) {
                var df:String = item.dataField.toString();
                if(df == "slideNo") {
                  thisDP = this.dataProvider;
                  var newSlideNo:int;
                  if(thisDP.length > 1) {
                 // looking for the last row of the DataGrid's dataProvider, but as 
                    // length is calculated differently in NewEntryArrayCollection.as 
                    // to account for the "dummy" row, we need to go back 2 rows.
                    newSlideNo = int(thisDP[thisDP.length -2].slideNo); 
                  } else {
                    newSlideNo = 1;
                  }
                  obj[df] = newSlideNo;
                } else if(df == "specimenNo") {
                  thisDP = this.dataProvider;
                  var newSpecimenNo:int;
                  if(thisDP.length > 1) {
                    newSpecimenNo = int(thisDP[thisDP.length -2].specimenNo) + 1;
                  } else {
                    newSpecimenNo = 1;
                  }
                  obj[df] = newSpecimenNo;
                } else {
                  obj[df] = "";
                }
              }
              return obj;
            }