Thursday, December 10, 2009

Date Sorting in DataGrid on Flex 1.5

Everybody who ever had data columns in a DataGrid in Flex 1.5 came across the combination of interesting sorting behavior when the column has a label function which formats the date to something like MM/DD/YYYY. I have tried various work arounds but the sortCompareFunction documentation is very clear:

“sortCompareFunction

sortCompareFunction: Function

Pointer to the callback function that gets called when the user click on a column header to sort the contents of the DataGrid. The underlying dataProvider will call this function multiple times in order to sort the elements of the dataProvider. If a labelFunction is defined, then the objects to be sorted are the return values of the labelFunction. The function signature of the callback function takes three parameters and should look like this:

mySortCompareFunction(obj1 : Object, obj2 : Object, columnIndex : Number) : Number

obj1 - a data element to compare
obj2 - another data element to compare with obj1
columnIndex - zero-based index of the column number that is being sorted

The function should return a value based on the comparison of the objects:

-1 if obj1 should appear before obj2 in the sorted sequence
0 if obj1 = obj2
1 if obj1 should appear after obj2 in the sorted sequence
The default value is undefined.”

There is no way around that when using a labelFunction.

Take for example this mxml application:

<?xml version="1.0" encoding="utf-8"?>

<mx:Application xmlns:mx="http://www.macromedia.com/2003/mxml" creationComplete="initApp()">

<mx:DateFormatter id="dateformat" formatString="MM/DD/YYYY"/>

<mx:Array id="items"/>

<mx:Script>

<![CDATA[

function initApp() {

items.push( {lbl:"First Item", dte:new Date(2009, 11, 6)});

items.push( {lbl:"Second Item", dte:new Date(2007, 5, 14)});

items.push( {lbl:"Third Item", dte:new Date(2008, 0, 9)});

items.push( {lbl:"Fourth Item", dte:new Date(2008, 8, 1)});

dgItems.dataProvider = items;

}

function displaylabel(item, col) {

return dateformat.format(item[col]);

}

]]>

</mx:Script>

<mx:DataGrid dataProvider="{items}" id="dgItems">

<mx:columns>

<mx:Array>

<mx:DataGridColumn columnName="lbl" headerText="Label"/>

<mx:DataGridColumn columnName="dte" headerText="Date"

labelFunction="displaylabel"/>

</mx:Array>

</mx:columns>

</mx:DataGrid>

</mx:Application>

If you try to sort this data grid by clicking on the Date header, the sorting is not performed correctly.

In order to circumvent this, I developed a little workaround. Basically the display in the DataGrid is handled by a cell renderer, and now the sorting can be done on the underlying Date objects.

Here is the cell renderer:

datesort.mxml

<?xml version="1.0" encoding="utf-8"?>

<mx:VBox xmlns:mx="http://www.macromedia.com/2003/mxml">

<mx:DateFormatter formatString="MM/DD/YYYY" id="dateformat"/>

<mx:Script>

<![CDATA[

import mx.controls.DataGrid;

var getCellIndex:Function;

var listOwner:DataGrid;

function setValue(str:String, item:Object, sel:String):Void {

if (item == undefined || item == null) return;

var col = listOwner.getColumnAt(getCellIndex().columnIndex).columnName;

datelabel.text = dateformat.format(item[col]);

}

]]>

</mx:Script>

<mx:Label id="datelabel" textAlign="center" width="100%"/>

</mx:VBox>

This cell renderer just takes a Date and displays this as a label.

Now we can build an application:

<?xml version="1.0" encoding="utf-8"?>

<mx:Application xmlns:mx="http://www.macromedia.com/2003/mxml"

creationComplete="initApp()"

>

<mx:Array id="items"/>

<mx:Script>

<![CDATA[

function initApp() {

items.push( {lbl:"First Item", dte:new Date(2009, 11, 6)});

items.push( {lbl:"Second Item", dte:new Date(2007, 5, 14)});

items.push( {lbl:"Third Item", dte:new Date(2008, 0, 9)});

items.push( {lbl:"Fourth Item", dte:new Date(2008, 8, 1)});

dgItems.dataProvider = items;

}

function sortdate(obj1:Object, obj2:Object, columnIndex:Number) : Number {

if (obj1.getTime() < obj2.getTime()) return -1;

if (obj1.getTime() == obj2.getTime() ) return 0;

if (obj1.getTime() > obj2.getTime() ) return 1;

return undefined;

}

]]>

</mx:Script>

<mx:DataGrid dataProvider="{items}" id="dgItems">

<mx:columns>

<mx:Array>

<mx:DataGridColumn

columnName="lbl"

headerText="Label"

/>

<mx:DataGridColumn

columnName="dte"

headerText="Date"

cellRenderer="datesort"

sortCompareFunction="sortdate"

/>

</mx:Array>

</mx:columns>

</mx:DataGrid>

</mx:Application>

Now the sorting on the date field is correct.