Create a jQuery Plugin to Sort and Paginate a Table

Posted by: Irvin Dominin , on 8/6/2015, in Category jQuery and ASP.NET
Views: 21840
Abstract: Create a jQuery plugin to Sort a table and also add Pagination to the table with Previous and Next links to move back and forth. We will use the jQuery Boilerplate plugin to create our plugin

In a previous article Using jQuery to Sort and Paginate a Table , we saw how on a table with hundreds of rows, we could use sorting and pagination to display a small subset of that data, at a given point of time. The navigation links would then allow users to move through the other subsets of data.

With our newly acquired plugin knowledge gained in Authoring your First jQuery Plugin and Create a jQuery Plugin to display a Running Counter, let’s build a sorting and pagination plugin for ourselves. Just as in our Authoring your First jQuery Plugin article, we will once again look up to the jQuery Boilerplate plugin to create our plugin. I strongly recommend you to read that article before you proceed further. Create a new file ‘SortandPaginateTablePlugin.html’. Also create separate folders called ‘scripts’ and ‘css’ on the root. The css used in this article can be downloaded from the source code mentioned at the end of this article.

 

 

The plugin will work on any table with a similar structure:

<body>
    <table border="1" id="movie">
        <thead>
            <tr>
                <th class="ranking">Ranking</th>
                <th class="title">Movie</th>
                <th class="year">Release Year</th>
            </tr>
        </thead>
        <tbody>
            <tr>
                <td>1</td>
                <td>Citizen Kane</td>
                <td>1941</td>
            </tr>
            ...
        </tbody>
    </table>

  
</body>

I won’t be explaining the sorting and pagination logic, as you can always refer to my previous article Using jQuery to Sort and Paginate a Table  to understand it better. I also won’t be explaining the plugin structure in depth as you can refer to Authoring your First jQuery Plugin for details. In this article, I will be focusing solely on creating the sorting and pagination plugin and explain you the important steps. Our intention is to be able to do the following:

$("table").sortpaginate();

..and any table with a similar structure should have sorting and pagination enabled on it.

Sorting and Pagination

Let us get started. Create a new file ‘jQuery.sortpaginate.js’ in the scripts folder. Also add the latest version of jQuery in the scripts folder. We start by defining our IIFE (refer to my previous article to know what an IIFE is) and set the plugin defaults.

; (function ($, window, document, undefined) {
    var pluginName = 'sortpaginate',
        defaults = {
            pageSize: 4
        };
    ...
})(jQuery, window, document);

Here we are setting the default page size to 4 in case the user does not specify a page size. The next step defines the plugin constructor as follows:

function Plugin(element, options) {
    this.element = element;
    this.options = $.extend({}, defaults, options);
    this._defaults = defaults;
    this._name = pluginName;
    this.init();
}

Then comes the logic associated with the plugin initialization. We will perform five important steps here:

  • Define the initial table state when the page loads
  • Define the Sorting logic
  • Define the Pagination logic
  • Handle Sorting
  • Handle Pagination

We can access the DOM element and the options via the current instance, using this.element and this.options.

Plugin.prototype.init = function () {
var _$this = $(this.element);
var _opts = this.options;

Define the initial Table state when the page loads

_$this.wrap("<div class='sp_wrapper'><div class='sp_table'>");
_$this.parent().after("<div class='sp_navigator'>");

_$this.closest(".sp_wrapper")
        .find(".sp_navigator")
        .append("<span class='sp_paginate sp_previous'><a href=''>Previous<a/></span>")
        .append("<span class='sp_paginate sp_next'><a href=''>Next<a/></span>");

function initTable() {
    $(_$this).find("tbody").attr("data-firstRecord", 0);

    $(_$this).closest(".sp_wrapper").find(".sp_navigator .sp_previous").hide();
    $(_$this).closest(".sp_wrapper").find(".sp_navigator .sp_next").show();

    (_$this).find("th").each(function () {
        var width = $(_$this).width() + 80;
        $(_$this).width(width);
    });

    $(_$this).find("th").eq(0).addClass("sp_sorted_asc");

    sortTable($(_$this), 0, "asc");

    paginate(parseInt($(_$this).find("tbody")
                .attr("data-firstRecord"), 10),
                _opts.pageSize);

}

The initialization logic is as follows:

Start by wrapping the table in a container:

_$this.wrap("<div class='sp_wrapper'><div class='sp_table'>");

and then insert the navigation container that will hold the anchor elements to navigate through the pages:

_$this.closest(".sp_wrapper")
        .find(".sp_navigator")
        .append("<span class='sp_paginate sp_previous'><a href=''>Previous<a/></span>")
        .append("<span class='sp_paginate sp_next'><a href=''>Next<a/></span>");

Set the initial table state to show the first page on load and hide the ‘Previous’ navigation link:

function initTable() {
    $(_$this).find("tbody").attr("data-firstRecord", 0);

    $(_$this).closest(".sp_wrapper").find(".sp_navigator .sp_previous").hide();
    $(_$this).closest(".sp_wrapper").find(".sp_navigator .sp_next").show();

Increment the table width to accommodate the sorting icons:

(_$this).find("th").each(function () {
    var width = $(_$this).width() + 80;
    $(_$this).width(width);
});

Put the first column in the ascending sort order:

$(_$this).find("th").eq(0).addClass("sp_sorted_asc");

Call the sortTable() function that we will define shortly, to sort the table using the current sorting order

sortTable($(_$this), 0, "asc");

..and finally the paginate() function that we will define shortly, to paginate through the records.

paginate(parseInt($(_$this).find("tbody")
            .attr("data-firstRecord"), 10),
            _opts.pageSize);

Define the Sorting logic

Here’s how our sortTable function looks like:

function sortTable(table, column, order) {
    var asc = order === 'asc';
    var tbody = table.find('tbody');

    tbody.find('tr').sort(function (a, b) {
        if (asc) {
            return $('td:eq(' + column + ')', a)
                .text()
                .localeCompare($('td:eq(' + column + ')', b).text());
        } else {
            return $('td:eq(' + column + ')', b)
                .text()
                .localeCompare($('td:eq(' + column + ')', a).text());
        }
    }).appendTo(tbody);
}

Here we are sorting the table using a custom sorting function by switching the rows order, then appending them to the table body. The code defines a sortTable() function that takes a table id, a column index (0-based) and a sorting direction. The sorting function uses jQuery sort() and eq() method. Refer Using jQuery to Sort and Paginate a Table to understand the sortTable() function in detail.

Define the pagination logic

var paginate = function (start, size) {
    var tableRows = $(_$this).find("tbody tr");
    var end = start + size;
    // Hide all the rows
    tableRows.hide();
    // Show a reduced set of rows using a range of indices.
    tableRows.slice(start, end).show();
    // Show the pager
    $(_$this).closest(".sp_wrapper").find(".sp_navigator .sp_paginate").show();
    // If the first row is visible hide prev
    if (tableRows.eq(0).is(":visible")) $(_$this).closest(".sp_wrapper").find(".sp_navigator .sp_previous").hide();
    // If the last row is visible hide next 
    if (tableRows.eq(tableRows.length - 1).is(":visible")) $(_$this).closest(".sp_wrapper").find(".sp_navigator .sp_next").hide();
}

In the paginate function, we calculate the page to display by using slice() method which reduces the set of matched elements to a subset specified by a range of indices. Refer  Using jQuery to Sort and Paginate a Table to understand the paginate() function in detail.

Handle Sorting

_$this.find("th").on("click", function () { 
    $(_$this).find("th").not($(this)).removeClass("sp_sorted_asc sp_sorted_desc");

    if ($(this).hasClass("sp_sorted_asc") || $(this).hasClass("sp_sorted_desc")) {
        $(this).toggleClass("sp_sorted_asc sp_sorted_desc");
    } else {
        $(this).addClass("sp_sorted_asc");
    }

    $(_$this).find("tbody tr").show();

    sortTable($(_$this), $(this).index(), $(this).hasClass("sp_sorted_asc") ? "asc" : "desc");

    // Start the pagination
    paginate(parseInt($(_$this).find("tbody").attr("data-firstRecord"), 10), _opts.pageSize);

});

When the user clicks on the header of a column, we remove the sort classes for all the columns, but not the current one:

$(_$this).find("th").not($(this)).removeClass("sp_sorted_asc sp_sorted_desc");

The next step is to set or swap the sort direction:

if ($(this).hasClass("sp_sorted_asc") || $(this).hasClass("sp_sorted_desc")) {
        $(this).toggleClass("sp_sorted_asc sp_sorted_desc");
    } else {
        $(this).addClass("sp_sorted_asc");
    }

Set the complete list of rows as visible:

$(_$this).find("tbody tr").show();

Sort the table in the current sorting order using sortTable():

sortTable($(_$this), $(this).index(), $(this).hasClass("sp_sorted_asc") ? "asc" : "desc");

and finally paginate:

paginate(parseInt($(_$this).find("tbody").attr("data-firstRecord"), 10), _opts.pageSize);

Handle Pagination

_$this.closest(".sp_wrapper").find(".sp_navigator").on("click", ".sp_paginate", function (e) {
    e.preventDefault();
    var tableRows = $(_$this).find("tbody tr");
    var tmpRec = parseInt($(_$this).find("tbody").attr("data-firstRecord"), 10);

    // Define the new first record
    if ($(this).hasClass("sp_next")) {
        tmpRec += _opts.pageSize;
    } else {
        tmpRec -= _opts.pageSize;
    }
    // The first record is < of 0 or > of total rows
    if (tmpRec < 0 || tmpRec > tableRows.length) return

    $(_$this).find("tbody").attr("data-firstRecord", tmpRec);
    paginate(tmpRec, _opts.pageSize);
});

When the user clicks on the navigation anchor links, prevent the default action, get a reference of all the table rows and find the data-attribute on the tbody element representing the current first row displayed on the table and maximum size of each page

_$this.closest(".sp_wrapper").find(".sp_navigator").on("click", ".sp_paginate", function (e) {
    e.preventDefault();
    var tableRows = $(_$this).find("tbody tr");
    var tmpRec = parseInt($(_$this).find("tbody").attr("data-firstRecord"), 10);

Define the new first record:

if ($(this).hasClass("sp_next")) {
    tmpRec += _opts.pageSize;
} else {
    tmpRec -= _opts.pageSize;
}

If the first record is less than 0 (first page) or greater than the total number of rows (last page), return:

if (tmpRec < 0 || tmpRec > tableRows.length) return

..otherwise paginate:

$(_$this).find("tbody").attr("data-firstRecord", tmpRec);
paginate(tmpRec, _opts.pageSize);

Now that we have the sorting and pagination logic in place, the initTable function is triggered to setup an initial display state of the grid.

initTable();

Here we paginate and use sortTable functions to sort the grid by the first column in ascending order and with a pagination of 4 rows per page.

The last step is to use the Plugin wrapper around the constructor

$.fn[pluginName] = function (options) {
    this.each(function () {
        // Check that the element is a table
        if (!$(this).is('table')) return

        if (!$.data(this, 'plugin_' + pluginName)) {
            $.data(this, 'plugin_' + pluginName,
            new Plugin(this, options));
        }
    });

    return this;
}

Within the function, this represents the jQuery object on which your function was called. That means that when you say $("table").sortpaginate(), the value of this refers to the jQuery object containing the result of $("table") which will contain all the tables on the page. By doing this.each, you are looping over every element within this, and thus sorting and paginating every table on the page (assuming the tables have the same structure defined at the beginning of this article).

Within this.each, we check to see if the element is a table:

if (!$(this).is('table')) return

..and if the table object has been assigned a plugin. If yes, that means the plugin is already up and running. In case we haven't, then we create a new Plugin() object and add it to the table element.

if (!$.data(this, 'plugin_' + pluginName)) {
    $.data(this, 'plugin_' + pluginName,
    new Plugin(this, options));
}

At the end of it, this is returned making your plug-in chainable.

That’s it. Your plugin is ready to be used.

You can call your plugin in the following manner:

<script type='text/javascript'>
    $(function () {
        $("table").sortpaginate();
    });
</script>

Save and browse the page and you will see that the table gets sorting and pagination capabilities.

The plugin can select multiple table elements, and all the tables are transformed and independent from each other. The plugin can be improved further by adding some options and event handlers. The sky is the limit here!

This code was authored by Irvin Dominin and explained by Irvin and Suprotim Agarwal

Did you like this article? This article was taken from a new jQuery book The Absolutely Awesome jQuery Cookbook which contains scores of similar practical jQuery/jQueryUI recipes you can use in your projects right away.

Live Demo: http://www.jquerycookbook.com/demos/S8-Plugins/63-SortandPaginateTablePlugin.html

Download the entire source code of this article (Github)

This article has been editorially reviewed by Suprotim Agarwal.

Absolutely Awesome Book on C# and .NET

C# and .NET have been around for a very long time, but their constant growth means there’s always more to learn.

We at DotNetCurry are very excited to announce The Absolutely Awesome Book on C# and .NET. This is a 500 pages concise technical eBook available in PDF, ePub (iPad), and Mobi (Kindle).

Organized around concepts, this Book aims to provide a concise, yet solid foundation in C# and .NET, covering C# 6.0, C# 7.0 and .NET Core, with chapters on the latest .NET Core 3.0, .NET Standard and C# 8.0 (final release) too. Use these concepts to deepen your existing knowledge of C# and .NET, to have a solid grasp of the latest in C# and .NET OR to crack your next .NET Interview.

Click here to Explore the Table of Contents or Download Sample Chapters!

What Others Are Reading!
Was this article worth reading? Share it with fellow developers too. Thanks!
Share on LinkedIn
Share on Google+

Author
Irvin Dominin is currently working as lead of a technical team in SISTEMI S.p.A. (Turin, Italy) on .NET, jQuery and windows Projects. He is an active member on StackOverflow. You can reach him at: irvin[dot]dominin[attherate]gmail[dot]com or on LinkedIn


Page copy protected against web site content infringement 	by Copyscape




Feedback - Leave us some adulation, criticism and everything in between!