Thursday 16th June, 2016

Dynamic enum sorting

Ordering of data is a fundamental aspect in any interactive table and it is core to DataTables' ability to let end users easily find the data they are looking for. DataTables has a number of built in ordering types for numbers, currency, dates and strings, but data is complex and it would be impossible for DataTables to define ordering options for all data sets. As such it provides a plug-in interface allowing you to define your own ordering rules for data.

In this post I will introduce a function that will dynamically create ordering and type detection plug-ins for an enumerated list (commonly shortened to enum).

This simple example table shows the use of the plug-in to order the priority column intuitively:

Project Name Priority
Skilled Drill High
Yellow Sleepy Uranium Low
Massive Tungsten Medium
Dusty Scissors High
Big Yard Low
Moving Backpack Low
Risky Cloud Medium

Why order enums?

While the data shown in the table might ultimately just be strings, those strings convey a lot of information that we intuitively understand. For example, if we consider the following three data points:

  • High
  • Medium
  • Low

We intuitively see the priority order associated with them. However, as strings, if they are ordered they would be displayed as: High, Low, Meidum causing confusion to end users. Using an enum ordering plug-in we can tell DataTables how we want the data to be ordered.

The need for a dynamic solution

Its fairly trivial to write a plug-in that would handle a known enumeration and hard code sorting values (i.e. "High" = 1, "Mdeium" = 2, etc), but we always want to follow the Don't Repeat Yourself (DRY) principle to keep our code small, clean and manageable, so we need a plug-in that will accept any enumeration that a developer requires.

The goal for our software therefore is to have a function that will accept an array of values that defines both the values in the enum and their order (ascending - the descending will be handled by DataTables automatically):

DataTable.enum( [ ... ] );

Mapping

Before diving into code, let's first consider how to implement the ordering and type detection plug-ins. In essence we simply need to check to see if a data point is in the source array, and if so what its position is. We could use Array.prototype.indexOf or jQuery's $.inArray to support older browsers, but these inevitably lead to a loop over the array's data which will hurt performance.

Instead we can create a reverse mapping object where the value is the property key allowing for fast lookup, and the property value is the index. Consider our [ 'High', 'Medium', 'Low' ] example - it would become:

{
    "High": 0,
    "Medium": 1,
    "Low": 2
}

The downside to this method is that in ES5 the object keys are strings only - so if you were using a complex object that doesn't easily convert to a unique string (.toString()) you might run into conflicts. ES6 introduces Map objects designed for exactly this sort of situation.

Modern browsers have quite good support for Map, but of course not all of our customer's use a greenfield browser, so we need to fallback to objects where appropriate.

The code for our mapping is thus simply:

var lookup = window.Map ? new Map() : {};

for ( var i=0, ien=arr.length ; i<ien ; i++ ) {
    lookup[ arr[i] ] = i;
}

Plug-ins

With the lookup map in place we can now create the type detection and ordering plug-ins for DataTables.

Type detection

DataTables' type detection plug-ins are attached to the DataTable.ext.type.detect array. This is an array of functions that DataTables will loop over, checking to see if the data in a column matches any of the given types.

In our case we can simply check if the lookup object contains a value for the data point DataTables is checking:

DataTable.ext.type.detect.unshift( function ( d ) {
    return lookup[ d ] !== undefined ?
        name :
        null;
} );

Note that unshift is used to prefix the type detection function to the start of the array to ensure that DataTables uses this type detection before any others.

Additionally note the use of the variable name which is a unique name for this data type. Its simply a counter with the prefix enum- to ensure that multiple enums can be used on a single page!

Ordering plug-in

Ordering plug-ins are attached to the DataTable.ext.type.order object, using the name of the type that the ordering method will apply to. DataTables has built in ordering comparison for numeric and string values, so all we need to do is create a plug-in that will returns a value that can be ordered - in this case the array index of the data point which is already in our lookup object:

DataTable.ext.type.order[ name+'-pre' ] = function ( d ) {
    return lookup[ d ];
};

Putting it all together

The result is that we have a really simple, but very versatile plug-in that can be used to sort any data in any order you require:

var unique = 0;

DataTable.enum = function ( arr ) {
    var name = 'enum-'+(unique++);
    var types = DataTable.ext.type;
    var lookup = {};

    for ( var i=0, ien=arr.length ; i<ien ; i++ ) {
        lookup[ arr[i] ] = i;
    }

    // Add type detection
    types.detect.unshift( function ( d ) {
        return lookup[ d ] !== undefined ?
            name :
            null;
    } );

    // Add sorting method
    types.order[ name+'-pre' ] = function ( d ) {
        return lookup[ d ];
    };
};

This plug-in can be used simply by copying the code from the above, or you can include it directly from the DataTales CDN.

JS

Feedback

As always, feedback is warmly welcomed. If you see any improvements that can be made, feel free to open a new forum discussion or fork the DataTables plug-ins repo and send a pull request with your changes.