Saturday, January 27, 2007

Sorting a DataGrid in Flex 2

The default sorting behavior of DataGrid needs some customization to suit the list pane of my remote file explorer. Rather than a simple alphabetical sort in every case, the behavior depends on the sort column:

  1. The last modified column should sort in chronological order.
  2. The file size column should sort by actual size (i.e. 1 KB, 500 KB and 1.5 MB should sort correctly).
  3. Text columns should sort in case-insenstive order.

The documentation for DataGridColumn mentions the optional sortCompareFunction property, which specifies a comparison function with the contract:

private function compare(a:Object, b:Object):int {
   return (a < b) ? -1 : (a == b) ? 0 : 1;
}

However, the documentation doesn't say what the arguments a and b actually are. Turns out that they are the data for the rows being compared, not the column values.

This is convenient for writing generic functions that can be applied to multiple columns. First I wrote a function to do a case-insensitive comparison for text columns:

/**
  * Compares items by the case-insensitive value of the current sort
  * column.
  **/
private function compareCaseInsensitive(a:Object, b:Object):int {

  var sortColumn:String = itemData.sort.fields[0].name;

  var valueA:String = (a as XML)[sortColumn].toString().toLowerCase();
  var valueB:String = (b as XML)[sortColumn].toString().toLowerCase();

  return (valueA < valueB) ? -1 : (valueA == valueB) ? 0 : 1;
}

The sort.fields collection indicates the current sort column. Note that the < and > operators work for strings as well as numbers in ActionScript.

For the Last Modified and Size columns of the list pane, I added a sortable value attribute to the XML data supplied by the server, i.e.:

<item>
  <title>clone</title>
  <size value="1024">1 KB</size>
  <type>Web Page</type>
  <lastModified value="200606241611">24 Jun 06 04:11 PM PDT</lastModified>
</item>

This convention allowed me to use a single comparison function for all columns that do not sort alphabetically:

/**
  * Compares items by the value attribute of the current sort column.
  **/
private function compareByValue(a:Object, b:Object):int {

  var sortColumn:String = itemData.sort.fields[0].name;

  var sizeA:int = (a as XML)[sortColumn].@value;
  var sizeB:int = (b as XML)[sortColumn].@value;

  return (sizeA < sizeB) ? -1 : (sizeA == sizeB) ? 0 : 1;
}

I applied these functions to the corresponding columns by modifying their declarations in the MXML file:

<mx:DataGridColumn dataField="title" headerText="Title" 
                   sortCompareFunction="compareCaseInsensitive"/>
<mx:DataGridColumn dataField="size" headerText="Size" 
                   sortCompareFunction="compareByValue"/>
<mx:DataGridColumn dataField="type" headerText="Type" 
                   sortCompareFunction="compareCaseInsensitive"/>
<mx:DataGridColumn dataField="lastModified" headerText="Modified" 
                   sortCompareFunction="compareByValue"/>

I tested this arrangement and it worked fine.