The Kendo UI Grid is a pretty awesome grid control, however if you have been following my series of “enhancements” (read : hacks) on the grid, I’m continuing to find things that are sorely missing.  This article is actually a result of a request I received from a reader asking if I could figure out how to get case insensitive searching to work on a local data source.  Since I was only using server-side sorting and paging I didn’t realize this was an issue.

The Forum Post Here : http://www.kendoui.com/forums/ui/grid/how-to-enable-case-insensitive-sorting-on-kendo-ui-grid.aspx basically says, yea it doesn’t do that, but if you can get enough people to ask for it, maybe someday it will.

Ok one person asked me – and now, as of today, you too can have case insensitive sorting using kendo UI grid with a local data source.  There actually wasn’t that much to making this work so this is going to be pretty short.

To begin were going to Hard code in a dataset:

        //-- Hard Coded Data Source
        var myData = [
            { name: "quam sit amet imperdiet.", desc: "ligula mattis scelerisque" },
            { name: "Quam sit amet imperdiet.", desc: "Ligula mattis scelerisque" },
            { name: "qaam sit amet imperdiet.", desc: "Ligula mattis scelerisque" },
            { name: "QAam sit amet imperdiet.", desc: "ligula mattis scelerisque" },
            { name: "suscipit viverra arcu bibendum eu.", desc: "latora torquent per conubia nostra" },
            { name: "Suscipit viverra arcu bibendum eu.", desc: "Litora torquent per conubia nostra" },
            { name: "sAscipit viverra arcu bibendum eu.", desc: "Litora torquent per conubia nostra" },
            { name: "Sascipit viverra arcu bibendum eu.", desc: "lztora torquent per conubia nostra" },
            { name: "risus tincidunt in rutrum nunc congue", desc: "gravida dapibus" },
            { name: "Risus tincidunt in rutrum nunc congue", desc: "Gravida dapibus" },
            { name: "rAsus tincidunt in rutrum nunc congue", desc: "Gravida dapibus" },
            { name: "Rasus tincidunt in rutrum nunc congue", desc: "gravida dapibus" },
        ];

Next we will initialize a KendoUI Grid to use this data as it’s local data source.

        var $grid = $("#kGrid");

        var gridDataSource = new kendo.data.DataSource({
            data: myData,
            pageSize: 5
        });

        var grid = $grid.kendoGrid({
            dataSource: gridDataSource,
            columns: [
                { field: "name", title: "Name Field", sortable: true },
                { field: "desc", title: "Desc Field", sortable: true }
            ],
            sortable: true,
            pageable: true
        });

Pretty straight foreword, so nothing to explain. 

So lets see the problem before we move on.  If you run this page you get the following when sorting:

image

Capitols first, lowercase second.  Yea that’s a perfectly acceptable sorting algorithm!

So lets do a little hacking.  First thing I want to do is to read through the dataset and create a lowercase (hidden) property for each field we will want to be sortable.  (underscore.js is being used for list iteration if your not familiar with the _.each())

        //Read through the datasource and create a lowercase col for each sortable col
        var gData = grid.data("kendoGrid");
        var ds = gData.dataSource;
        var sortableCols = [];
        $(".k-header[data-role='sortable']").each(function () {
            var $sortHeader = $(this);
            sortableCols.push($sortHeader.attr('data-field'));
        });
        _.each(ds._data, function (dItem) {
            _.each(sortableCols, function(sCol){
                eval("dItem." + sCol + "_lower = dItem." + sCol + ".toLowerCase()");
            });
        });

So what did we do?  We went through each header column that has the data-role of sortable and we got the property name in the dataset.  We then stepped through each item in the data source and created a sister field for each sortable column named colName_lower and filled it with the lowercase value of the field.

Next were going the add an onClick listener to the table header of sortable rows so we can implement our own sorting.  Steps are described in the comments below:

        //On Sortable Row Click re-sort using the lowercase field
        $(".k-header[data-role='sortable']", $grid).click(function (e) {
            var $sortHeader = $(this);
            var sortOn = $sortHeader.attr('data-field');
            var sortOrd = 'asc';

            //Get current sorting settings
            var curSortOn = grid.attr('data-sympletech-curSortOn');
            var curSortOrd = grid.attr('data-sympletech-curSortOrd');

            //Toggle sort order
            if (curSortOn == sortOn && curSortOrd == 'asc') {
                sortOrd = 'desc';
            }

            //Add in a tracking property so we can keep track of how we last sorted the grid
            grid.attr('data-sympletech-curSortOn', sortOn);
            grid.attr('data-sympletech-curSortOrd', sortOrd);

            //Perform the sort on lowercase property
            ds.sort({ field: sortOn + "_lower", dir: sortOrd });
        });

One efficiency issue I must admit here – is the grid will actually be sorted twice – once using the non-normalized column which seems to fire before the click even then a second time using the click event above.  If anyone knows of a way to prevent the 1st sort feel free to suggest it.

And now like magic sorting is working the way you would expect it to.

image

The Next thing we need to do it to draw the sorting arrows in the header field (the image above has them as it’s from a previous iteration of this code – that did not work with paging, if you run what I’ve laid out so far you will get sorting but no arrows). 

After trying multiple approaches to do this I finally figured it out by setting up a DOM Mutation Listener – which is totally a dirty and terrible way to do this – that listens for the html of the grids visible page to change, then it redraws the arrows.

To start I used a slightly modified version of Jame’s Padolsey’s script found here: http://james.padolsey.com/javascript/monitoring-dom-properties/

    jQuery.fn.watch = function (id, fn) {
        return this.each(function () {
            var self = this;
            var oldVal;
            if (id === 'html') {
                oldVal = jQuery(this).html();
            } else {
                oldVal = self[id];
            }

            $(self).data(
                'watch_timer',
                setInterval(function () {
                    var newVal;
                    if (id === 'html') {
                        newVal = jQuery(self).html();
                    } else {
                        newVal = self[id];
                    }
                    if (newVal !== oldVal) {
                        fn.call(self, id, oldVal, self[id]);
                        oldVal = newVal;
                    }
                }, 100)
            );
        });
        return self;
    };

The only changes I made from the original is adding the ability to monitor the inner HTML of an object as well as the attributes.

Next we register the listener and use our custom data-attributes to determine which column is being sorted on and what direction.

        //If the contents of the visible grid page changes re-draw the sort aarows
        $('.k-grid-content tbody', $grid).watch('html', function (propName, oldVal, newVal) {
            var curSortOn = $grid.attr('data-sympletech-curSortOn');
            if (curSortOn != null) {
                var curSortOrd = $grid.attr('data-sympletech-curSortOrd');

                var iconName = "k-i-arrow-n";
                if (curSortOrd == 'desc') {
                    iconName = "k-i-arrow-s";
                }

                var $sortHeader = $grid.find('th[data-field = "' + curSortOn + '"]');
                $sortHeader.remove('.k-icon');
                $sortHeader.find('.k-link').append('<span class="k-icon ' + iconName + '"></span>');
            }
        });

And with now were able to see the arrows and page through our grid persisting the sorting settings.

image

image

Final Code

<script type="text/javascript">
    //-- http://james.padolsey.com/javascript/monitoring-dom-properties/
    jQuery.fn.watch = function (id, fn) {
        return this.each(function () {
            var self = this;
            var oldVal;
            if (id === 'html') {
                oldVal = jQuery(this).html();
            } else {
                oldVal = self[id];
            }

            $(self).data(
                'watch_timer',
                setInterval(function () {
                    var newVal;
                    if (id === 'html') {
                        newVal = jQuery(self).html();
                    } else {
                        newVal = self[id];
                    }
                    if (newVal !== oldVal) {
                        fn.call(self, id, oldVal, self[id]);
                        oldVal = newVal;
                    }
                }, 100)
            );
        });
        return self;
    };

    $(function () {
        //-- Hard Coded Data Source
        var myData = [
            { name: "quam sit amet imperdiet.", desc: "ligula mattis scelerisque" },
            { name: "Quam sit amet imperdiet.", desc: "Ligula mattis scelerisque" },
            { name: "qaam sit amet imperdiet.", desc: "Ligula mattis scelerisque" },
            { name: "QAam sit amet imperdiet.", desc: "ligula mattis scelerisque" },
            { name: "suscipit viverra arcu bibendum eu.", desc: "latora torquent per conubia nostra" },
            { name: "Suscipit viverra arcu bibendum eu.", desc: "Litora torquent per conubia nostra" },
            { name: "sAscipit viverra arcu bibendum eu.", desc: "Litora torquent per conubia nostra" },
            { name: "Sascipit viverra arcu bibendum eu.", desc: "lztora torquent per conubia nostra" },
            { name: "risus tincidunt in rutrum nunc congue", desc: "gravida dapibus" },
            { name: "Risus tincidunt in rutrum nunc congue", desc: "Gravida dapibus" },
            { name: "rAsus tincidunt in rutrum nunc congue", desc: "Gravida dapibus" },
            { name: "Rasus tincidunt in rutrum nunc congue", desc: "gravida dapibus" },
        ];

        var $grid = $("#kGrid");

        var gridDataSource = new kendo.data.DataSource({
            data: myData,
            pageSize: 5
        });

        var grid = $grid.kendoGrid({
            dataSource: gridDataSource,
            columns: [
                { field: "name", title: "Name Field", sortable: true },
                { field: "desc", title: "Desc Field", sortable: true }
            ],
            sortable: true,
            pageable: true
        });

        //Read through the datasource and create a lowercase col for each sortable col
        var gData = grid.data("kendoGrid");
        var ds = gData.dataSource;
        var sortableCols = [];
        $(".k-header[data-role='sortable']", $grid).each(function () {
            var $sortHeader = $(this);
            sortableCols.push($sortHeader.attr('data-field'));
        });
        _.each(ds._data, function (dItem) {
            _.each(sortableCols, function(sCol){
                eval("dItem." + sCol + "_lower = dItem." + sCol + ".toLowerCase()");
            });
        });

        //On Sortable Row Click re-sort using the lowercase field
        $(".k-header[data-role='sortable']", $grid).click(function (e) {
            var $sortHeader = $(this);
            var sortOn = $sortHeader.attr('data-field');
            var sortOrd = 'asc';

            //Get current sorting settings
            var curSortOn = grid.attr('data-sympletech-curSortOn');
            var curSortOrd = grid.attr('data-sympletech-curSortOrd');

            //Toggle sort order
            if (curSortOn == sortOn && curSortOrd == 'asc') {
                sortOrd = 'desc';
            }

            //Add in a tracking property so we can keep track of how we last sorted the grid
            grid.attr('data-sympletech-curSortOn', sortOn);
            grid.attr('data-sympletech-curSortOrd', sortOrd);

            //Perform the sort on lowercase property
            ds.sort({ field: sortOn + "_lower", dir: sortOrd });
        });

        //If the contents of the visible grid page changes re-draw the sort aarows
        $('.k-grid-content tbody', $grid).watch('html', function (propName, oldVal, newVal) {
            var curSortOn = $grid.attr('data-sympletech-curSortOn');
            if (curSortOn != null) {
                var curSortOrd = $grid.attr('data-sympletech-curSortOrd');

                var iconName = "k-i-arrow-n";
                if (curSortOrd == 'desc') {
                    iconName = "k-i-arrow-s";
                }

                var $sortHeader = $grid.find('th[data-field = "' + curSortOn + '"]');
                $sortHeader.remove('.k-icon');
                $sortHeader.find('.k-link').append('<span class="k-icon ' + iconName + '"></span>');
            }
        });
    });
</script>

<div id="kGrid"></div>

Hope you found this helpful, I’m sure it’s only a matter of time before this bug is fixed in Kendo UI, in the meantime feel free to use my enhancement.

Tagged with →  
Share →

3 Responses to How to enable case insensitive sorting on the kendo UI grid

  1. Jason says:

    It seems pretty crazy that Kendo hasn’t implemented a fix for case-insensitive sorting. I have run into another issue and wonder if you have a work around for that.
    When I have a column of numbers (Age for example), if there are null values, nulls and zeros are treated the same. I would think NaN/undefined/null should come before 0, 1, 2, 3 but that is not the case and I end up with [0, null, null, 0, 0, null, 1, 2, 3, ... ] I have a template where I replace null with an empty string but the error is still present.
    I haven’t tested with date columns but I assume there is an issue there as well.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>