DataTables: Ultimate Date and Time Sorting Plugin Bug

2015-02-23 JavaScript

Story

Today I found an interesting issue related to a great plug-in made by Allan Jardine called DataTables. DataTables gives you dozens of opportunities to turn your static HTML tables into interactive ones that support client side pagination, column sorting and dynamic row filtering.

I had to implement DataTables feature into one, particular table where one columns contains date in a different format. After browsing some resources, I had to extend DataTables with Ultimate date plug-in to support custom date format (in my case DD/MM/YYYY).

I’ve included required Moment.js and added

$.fn.dataTable.moment('DD/MM/YYYY');

before

// DataTables init
$('#my_example_table').DataTable({
    "pageLength"	: 50,
    "language"		: { "search": "Filter" },
    "columnDefs"	: [ { "targets": 5, "orderable": false } ]
});

everything was working like a charm until I had to concatenate cells (that were holding dates) with some additional HTML. In this case I had to append couple of icons by inserting some img tags.

<td>
    08/01/2015<br />
    <img src="icon.png" title="Icon 1" alt="" />
    <img src="icon.png" title="Icon 2" alt="" />
</td>

No rocket science as you see. But that broke the sorting feature. Whilst

08/01/2015

works perfectly, an example with anything after date string just don’t. I took an approach to review the Ultimate date plug-in and I wrote a fix to overcome the issue.

Function that holds a bug is called moment and takes two arguments – format (mandatory) and locale (not required).
As you saw before I pass my custom date format into first argument as DD/MM/YYYY. Later the script goes through every cell of the table and scans the content (d variable) to detect if that is a valid date.

// Add type detection
types.detect.unshift( function ( d ) {

    // Null and empty values are acceptable
    if ( d === '' || d === null ) {
        return 'moment-'+format;
    }

    return moment( d, format, locale, true ).isValid() ?
        'moment-'+format :
        null;
} );

Next, the same d variable is used to set proper sorting.

// Add sorting method - use an integer for the sorting
types.order[ 'moment-'+format+'-pre' ] = function ( d ) {
    d = moment_fix( d, format, locale );

    return d === '' || d === null ?
        -Infinity :
        parseInt( moment( d, format, locale, true ).format( 'x' ), 10 );
};

Since the string

08/01/2015<br />
<img src="icon.png" title="Icon 1" alt="" />
<img src="icon.png" title="Icon 2" alt="" />

is not a valid date the d variable should check just a substring whether it is a valid date.

Patch

Below is my short function.

moment_fix = function ( d, format, locale ) {
    var temp_d = d.substring(0, format.length);
    var valid = moment( temp_d, format, locale, true ).isValid() ? true : false;

    if (valid === true) {
        return temp_d;
    }

    return d;
};

Explanation

I take cell’s content, format and locale as arguments. Then I create a substring that contains just the date – Characters from 0 to the date format length. In this case format D) has 10 characters and contains pure and valid date. I use moment function to check if this is true or not. If not, then I return whole cell content but if temp_d holds valid date then I return just this substract and that fixes the problem.

Then obviously I have to assign what I return back to d variable

d = moment_fix( d, format, locale );

Whole fixed plugin is presented below.

(function($) {

$.fn.dataTable.moment = function ( format, locale ) {
	var types = $.fn.dataTable.ext.type;

	moment_fix = function ( d, format, locale ) {
		var temp_d = d.substring(0, format.length);
		var valid = moment( temp_d, format, locale, true ).isValid() ? true : false;

		if (valid === true) {
			return temp_d;
		}

		return d;
	};

	// Add type detection
	types.detect.unshift( function ( d ) {
		d = moment_fix( d, format, locale );

		// Null and empty values are acceptable
		if ( d === '' || d === null ) {
			return 'moment-'+format;
		}

		return moment( d, format, locale, true ).isValid() ?
			'moment-'+format :
			null;
	} );

	// Add sorting method - use an integer for the sorting
	types.order[ 'moment-'+format+'-pre' ] = function ( d ) {
		d = moment_fix( d, format, locale );

		return d === '' || d === null ?
			-Infinity :
			parseInt( moment( d, format, locale, true ).format( 'x' ), 10 );
	};
};

}(jQuery));

Please keep in mind that this fix is only for cells where date and additional content is after it, not before.

Do NOT follow this link or you will be banned from the site!