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.

Friday, June 19, 2009

Loading Library without library path

I have been struggling lately with loading dll's which I need to make an application work. Setting the library path -Djava.library.path=some/path/to/dll was not really an option as our application is distributed via Webstart where all the resources need to be needly packaged in a couple of jars.

I decided to take a hard look at the System.load() command where you pass a string to the dll location. With a little bit of help from class loaders, this can work.

I stored a DLL in the package run.dll with the name of test.dll

The following code snippet will find the location of the resource:

private static final String SUFFIX = ".dll";
public static String loadDLL(String name) throws Exception {
//result variable
String result = null;

//package name can be loaded with '/' or '.'
if (name.startsWith( "/" ) ) { name = name.substring(1); }

//add the .dll to the end of the name
if (!name.endsWith(SUFFIX) { name = name.concat(SUFFIX); }

//replace '.' with '/'
name = name.replace('.', '/');

//use the default class loader
ClassLoader loader = ClassLoader.getSystemClassLoader();

//get the resource
java.net.URL = loader.getResource(name);

//the URl file format is /drive:/path/to/dll/dll.dll
//therefore, strip the first '/' and replace the remaining
//'/' with whatever file separator is applicable for
//your OS
result = url.getFile().substring(1).replace("/", File.separator);

//path to resource
return result;
}

This can now be launched as:
public static void main (String args[]) {
try {
//now we can load the dll using system.load();
system.load ( loadDLL("some.pkg.test.dll") ) ;
} catch (Exception e) {
e.printStackTrace();
}
}