Wednesday, July 27, 2011

Spark Data Grid Tab Focus Control

After spending so many months customizing the AdvancedDataGrid, I'm now interested to see if I can migrate many of those customizations to the Spark DataGrid. One of my first challenges is to see if I can improve my focus control - specifically tab navigation.

To review, here is the use case I'm trying to achieve (BTW it works in spark, and only partially works in ADG). My data is a product, and I have two occasionally editable fields related to this product: Quantity and Price (my role is a sales associate - so I can negotiate on prices). The product as a whole is only editable if it is in stock; I cannot change quantity or price if it is out of stock. Price is only editable if and handful of business rules apply (margin, vendor, etc). I want to be able to tab to the editable fields.

The problem with the ADG was that in it's "findNextEditableItemRenderer" loop, it only checked the data (ie, inStock). There was no way to tell what column it was looking at.

I was quite pleased with the spark DataGrid and its nice separation of concerns. I love that there is a separate DataGridEditor. And I was happy to discover that there was a simple PUBLIC method on DataGrid that I could tap into to do everything that I wanted (So far).

That public method is startItemEditorSession(rowIndex:int, columnIndex:int):Boolean. SWEET!

That problem is resolved in the spark dataGrid.
You can see the example here. In this example, the top grid is the regular one and the bottom grid is the modified one. I'm using the alpha property instead of enabled to prove that focus is indeed under my control (MUH HA HA HA - <evil laugh>).

The key is that within the startItemEditorSession, you
  • convert the rowindex to the data via the getItemAt on the dataprovider
  • convert the columnIndex to the column
  • validate the data as well as the data[column.datafield] properties
  • return the boolean if editing should happen.

    And here is the code

    public class FocusSparkDataGrid extends DataGrid 
     {
      private var _isDataEditableHelper:IIsDataEditable
      
      public function FocusSparkDataGrid()
      {
       super();
      }
      
    
      public function get isDataEditableHelper():IIsDataEditable
      {
       return _isDataEditableHelper;
      }
    
      public function set isDataEditableHelper(value:IIsDataEditable):void
      {
       _isDataEditableHelper = value;
      }
    
      override public function startItemEditorSession(rowIndex:int, columnIndex:int):Boolean
      {
       var dataIsEditable:Boolean = isDataEditable(rowIndex,columnIndex);
       if (dataIsEditable)
       {
        var editingStarted:Boolean = super.startItemEditorSession(rowIndex,columnIndex);
        return editingStarted
       }
       return false;
      }
    
      
      protected function isDataEditable(rowIndex:int, columnIndex:int):Boolean
      {
       if (isDataEditableHelper != null)
       {
        var dataItem:Object = dataProvider.getItemAt(rowIndex);
        var column:GridColumn = GridColumn(columns.getItemAt(columnIndex));
        var dataField:String = column.dataField;
        return isDataEditableHelper.isDataEditableForProperty(dataItem,dataField)
       }
       return false;
      }
    }
    
    

    public class IsProductVOEditable implements IIsDataEditable
     {
      public function IsProductVOEditable()
      {
      }
      
      public function isDataEditableForProperty(data:Object, property:String=""):Boolean
      {
       var vo:ProductVO = ProductVO(data);
       if (vo.inStock)
       {
        if (property == "price")
        {
         return vo.priceEditable;
        }
        return true;
       }
       return false;
      }
     }
  • 8 comments:

    1. Hello, thanks for your post, it will surely come in handy.
      I have a further question though: how did you manage to get those TextInput's to work in your grid? I tried all the obvious ways but I couldn't be able to make them work.
      Thank You,

      Gabriele

      ReplyDelete
    2. Here is the code for a single column:

      <s:GridColumn dataField="quantity" editable="true" rendererIsEditable="true" >
      <s:itemRenderer>
      <fx:Component>
      <s:GridItemRenderer>
      <s:TextInput
      text="{data.quantity}"
      enabled="{data.inStock}"
      verticalCenter="0"
      horizontalCenter="0"
      />
      </s:GridItemRenderer>
      </fx:Component>
      </s:itemRenderer>
      </s:GridColumn>

      ReplyDelete
    3. Uhm.. I tried a similar code, but changes to the TestInput text value wouldn't get saved back to the dataprovider. To make them persistent I had to add an event handler on focusOut, like this:

      <s:itemRenderer>
      <fx:Component>
      <s:GridItemRenderer>
      <s:TextInput id="editor"
      text="{data[column.dataField]}" focusOut="data[column.dataField] = editor.text"/>
      </s:GridItemRenderer>
      </fx:Component>
      </s:itemRenderer>

      I also tried with a two-way binding, setting text to something like "@{data.field}", but that makes the textinput behave strangely.
      What do you think is the better approach?

      ReplyDelete
    4. If you are using renderIsEditable=true, then yes you need to roll your own methods to save the data back into the dataprovider. Otherwise, within your itemEditor (which needs to implement IGridItemEditor, or extend GridItemEditor or DefaultGridItemEditor), there is a save method that will take care of that for you.

      I would not recommend two-way binding in this case, unless you are sure that there is no way to get invalid data from the user's input (empty strings, numbers, characters, code injection?).

      ReplyDelete
    5. can you just give us the code of your first dataGrid (not the custom one). I'm trying to use a tab key as in your dataGrid but it doesn't work and the focus get out from the dataGrid to other components in the page.

      Thank you for your help.

      ReplyDelete
    6. @sld. If your regular grid isn't working, make sure that you have the grid and the column editable="true". Tabbing should work by default.

      ReplyDelete
    7. Hi this article is very helpful and gone through eg. which reaches my requirement but for ADG, i tried to implement the same stuff but i got errors can i get entire application so that i can follow your code thanks in advance..

      ReplyDelete
      Replies
      1. Vc, be sure to check out the following post. http://squaredi.blogspot.com/2011/09/precision-focus-control-within-spark.html, it contains an updated version, source, and description.

        Cheerio!
        Drew

        Delete