Data table pagination

This post was planned to show the custom pagination that we created to the jQuery DataTable. At first we had only one custom pagination. A unique pagination.

We created a function to create pagination with 4 callbacks. Callbacks for getting the next and previous pages and hasNextPage, hasPreviousPage. These callbacks communicate directly with functions of the angular controller. Which functions call the server to get the new data of one page according to the actual settings.

But meanwhile I write these blog posts the data table implementation has changed according to the new requirements.
After using this version of pagination we had two projects where we wanted to use the features of our data table implementations but with other pagination without server side connection. It was not so easy because some other parts of our earlier implementation was based on this custom pagination and removing this one and using the original pagination of the jQuery DataTable was not available.

Now we have two custom types of pagination. : )

In this post we will go through the steps of creating own pagination. After reading this post hopefully you will be able to create easily and fast your own pagination. If you know the principles of it, it isn’t a big deal anymore.

Where to start?

The place where you can define the pagination type of the data table is the creation method of it:

                    var table = $(tableId).dataTable({
                        "aoColumns": headerTitles,
                        "aaData": rows,
                        "iDisplayLength": pageSize,
                        "fnInfoCallback": onInfoChanged,
                        "sPaginationType": pagination, 	//my implementation in dataTablesExtensions
                        ...
                    });

“pagination” here is only a string. The name of the pagination. This name is the registered function name what we selected to our pagination. It needs to be unique and we have to be careful not to choose same names that are already defined to the jQuery DataTable. We used the “my_” prefix to our implementations.

How to define?

We need to register our pagination objects to the jQuery DataTable’s implementation and we can use the following way to do that:

$.fn.dataTableExt.oPagination.my_button_name = {…};

We can define this pagination where we want. We choose a separated dataTableExtensions js file but it can be defined in the same file as the data table creation file or you can place it anywhere you want.

The registered pagination needs to have two functions:

  • fnInit – To initialize DOM elements required for pagination with a list of the pages, called only at initialization time
  • fnUpdate – To update the list of page buttons, called after every change of the data table

fnInit

fnInit : function ( oSettings, nPaging, fnCallbackDraw ) {...}

In this function we get the nPaging as the second parameter. This is the UI section where we want to add our buttons.

In our both implementation we use the ul-li-a combination of controls to have the required Twitter Bootstrap style.

blogpost

We used jQuery to create the UI elements and append to each other.

var pager = $('<ul class="pager">').appendTo(nPaging);

            console.log(pager);

            var nPreviousWrapper = $('<li id="previousPager">').appendTo(pager);//.addClass("paginate_enabled_previous");
            var nNextWrapper = $('<li id="nextPager">').appendTo(pager);//.addClass("paginate_enabled_next");

            var nPrevious = $('<a>').text("Previous").appendTo(nPreviousWrapper);
            var nNext = $('<a>').text("Next").appendTo(nNextWrapper);

Now we have the UI elements and we need to add the functionality to the “buttons” we defined.

One way is to append our callback functions to them.

            //click event subscribe on previous
            nPrevious.click(previousCallback);

            //click event subscribe on next
            nNext.click(nextCallback);

Or you can use the original implementation to get the original pagination functionality. (with custom styled UI)

            oSettings.oApi._fnBindAction( nPrevious, {action: "previous"}, fnClickHandler );
            oSettings.oApi._fnBindAction( nNext,     {action: "next"},     fnClickHandler );

In both example the nPrevious and the nNext are the jQuery elements we defined.

In the first version we register the callbacks to the click events of the “buttons”.
In the other version we use the original mechanism of the jQuery DataTable. You can create the fnClickHandler using the oSettings, first parameter of the fnInit function and the fnCallbackDraw, the third parameter of the fnInit function.

fnUpdate

fnUpdate: function (oSettings) {...}

In both of our implementations we only wanted to show / hide the buttons according to the first / last page of the table. At the first page hide the previous button and at the last page hide the next button so easy. It is only a switch between two CSS classes. We created a helper method to do the switch according to the result of a callback.

(function ($) {
    $.fn.switchClass = function (conditionCallback, classA, classB) {

        var classToAdd;
        var classToRemove;

        if (conditionCallback()) {
            classToAdd = classA;
            classToRemove = classB;

        } else {
            classToAdd = classB;
            classToRemove = classA;
        }

        $(this).removeClass(classToRemove);
        $(this).addClass(classToAdd);
    };
})(jQuery);

With the callbacks I only check that we have a previous and/or next page and if one direction is not available I hide the button from the UI.

Tip

fnUpdate is called at the creation time too! It is important because you don’t have to add the default classes to the controls. fnUpdate will be called and add the right classes to the controls according to the actual state at initialization time as well.
First time there is no previous page so it hides the button from the user.

Sources

You can read about the principles of creating custom pagination in the documentation of the jQuery DataTable and you can check our implementation on github.

Advertisements

Customise data table

In this post I will show how to build the data table in to your app and some basic customization of the data table.

We had a lot of requirements what we expected from our data table implementation.

  • Custom data source – at a time we have only the data of one page
  • Hiding columns
  • Ordering by header

These are really core functionalities but according to our special data source all of this functionalities needed a little trick to work fine.
After reading this post you will be able to form the original data table according to your needs like we could do it as well.

Let’s start from the beginning with adding the rows and the header titles to the table.

The original define method

It is very easy to get done with dataTables. You can do it at creation time with setting the property ‘aoColumns’ for header titles and the property ‘aaData’ for all the rows of the table.

$("#my-data-table").dataTable({
                        "aoColumns": headers,
                        "aaData": rows });

Both properties wait an array of primitive types. Therefore, if you work with JSON objects then you need to convert the objects and you will lost your origin objects in the table. If you want to handle later one row from the table first you have to convert it back to an object. But we will check how to handle this.

For converting the objects of the data source to the correct form for the data table we have two methods a ‘prepareHeader’ and a ‘prepareRows’.

To illustrate the conversion we will use in this post the data from the sample app that you can get from github.
In this sample app it is used a fake data source and the data is in the following format:

 	    { id : 3, engine: "Trident", browser: "Internet Explorer 6.0", platform: "Win 98+", version: 6, grade: "A" },
            { id : 4, engine: "Trident", browser: "Internet Explorer 7.0", platform: "Win XP SP2+", version: 7, grade: "A" },
            { id : 5, engine: "Gecko", browser: "Firefox 1.5", platform: "Win 98+ / OSX.2+", version: 1.8, grade: "A" },
            { id : 8, engine: "Webkit", browser: "Safari 1.2", platform: "OSX.3", version: 125.5, grade: "A" }

Header

First I create the collection of the header title.

                var prepareHeader = function (header) {

                    headers = [];

                    var sortables = scope[attrs.sortingProperties];
                    var toHide = scope[attrs.hidingProperties];

                    //get the property names for header titles
                    for (var item in header) {

                        if (!toHide || toHide.indexOf(item) == -1) {

                            var sortable = attrs.allSortable == "" ? true : false;

                            if (sortables && sortables.indexOf(item) != -1) {
                                sortable = true;
                            }

                            headers.push({ "sTitle": item, "bSortable": sortable });
                        }
                    }
                }

The parameter of this function is the first row of the data source collection. To set the title of the headers I get the property names from the first object of the data source.

It has only one restriction that the first object has to contain all of the properties we want to show in the table. If the object of the first row contains only 3 properties and all of the other objects have 5, the table will show only 3.

Sorting and hiding

As you could see in the method it was some extra. The option to hide properties from the data table and the option to sort by a property.

Through the API of the directive the user can define ‘sortingProperties’ and ‘hidingProperties’ as HTML attributes. Each property requires an array of strings.
(From the last commit I support case-insensitivity by giving the name of the property inside the array.)

In the directive I get the two arrays from the scope. (The scope in my directive is the “main” scope coming from the controller.) I can get the arrays through indexing the scope with the name of the array coming from the ‘attrs’ property of the directive.

I walk through all of the header elements and if the certain property name is not part of the property names in the ‘to hide’-collection than I add it to the headers.

Before adding the property name actually to the headers I check if the user wants to have the functionality to order by this property or not. According to the chose? I set the ‘bSortable’ property of the header to true or to false.
If the ‘sortingProperties’ collection is empty than I set false to ‘bSortable’ for all of the header elements.

The last step in the defining the header titles process is to add the result of this function to the data table with setting the ‘aoColumns’ property value to this array.

Rows

From all of the other objects in the source collection we can create the rows.

var prepareRows = function (source) {

                    var rows = [];
                    var toHide = scope[attrs.hidingProperties];

                    for (var i = 0; i < source.length; i++) {

                        var row = [];

                        for (var item in source[i]) {

                            if (!toHide || toHide.indexOf(item.toLowerCase()) == -1) {
                                row.push(source[i][item]);
                            }
                        }

                        rows.push(row);
                    }

                    return rows;
                }

In this method we use only the values of the properties. One object will be represented with an array of property values.

‘aaData’ waits a two dimensional array. The array represent the rows of the data table and the arrays in the array are the individual rows containing the property values. In default case.

Handling the data change

We want to set the header rows only once but the rows inside the table are changing according to the new settings. Pagesize, new page, sorting and so on.

At the certain time we have only rows of one page according to all of the settings (page size, page index, order, sort). But only this amount of the data comes from the server.

At the time of the data table creation we have only the data of the first page. The first page has the default settings. (Page size 10, page index 0, no order, no filter.)
And every time the user set something we send a request to the server and get the data of the new page with the new settings.

Because of this we have two functions. One to create the first page and a refresh function to clear the actual page and set the new rows.

The first function gets the source of one page and gets the values of the properties from the objects and if the user doesn’t want to hide the column it will add it to the table.

		var prepareRows = function (source) {

                    var rows = [];
                    var toHide = scope[attrs.hidingProperties];

                    for (var i = 0; i < source.length; i++) {

                        var row = [];

                        for (var item in source[i]) {

                            if (!toHide || toHide.indexOf(item) == -1) {
                                row.push(source[i][item]);
                            }
                        }

                        rows.push(row);
                    }

                    return rows;
                }

Refreshing the data clears the actual page with the built-in function ‘fnClearTable’. After we have an empty table it will prepare the rows with the function used for the first page to paste them into the table.
Last step to add the rows to the table with the built-in ‘fnAddData’ function and decorate the rows if needed. Decoration means inline buttons and setting the row selection mechanism.

var refreshData = function (source) {

                    originalSource = source;

                    //check if it is the end of the elements or probably it is more
                    elementsMaxReached = source.length < pageSize;

                    table.fnClearTable();

                    var rows = prepareRows(source);

                    table.fnAddData(rows);

                    decorateDataTableOnChange();
                };

The word “probably” in the comment is because our server side mechanism. We send the request but we can’t decide forward that it will give us more data back or not.
If the length of the data from the server is less than the actual page size it is obvious that it has no more data but it has the chance that we get data with the length that is equal with the page size but there is no more data on server.
But at the time we have no info about it. We will send a request to get the next page and we get nothing back. We have to deal with this situation.

Fortunately, if you give the dataTables an empty collection of rows with the ‘fnAddData’ than it will handle it.
It will write out “No data available in table” and now we know that there is no more page and will show only the previous navigation button.
Problem solved.

And we have the data table filled with the data of one page and the mechanism to change that on request.
The main request is the paging itself. The next post will be about it.

One last thing to this post. I mentioned it earlier that you loose the original object in the data table. Of course it is not acceptable. We need to get back our object for edit, for delete, for select and so on.

Two way connection between the original source and the data table

But how can we get back our original objects? – you would probably ask.

It could be easy. We need to store the original data inside the directive and we can later get the objects from it identified with the _id property of the row.

But in our case we could not choose this option because we didn’t want to show the _id in the table so it is a hidden property and not contained by the row at all.

But dataTables has the functionality to store the original order. So you can index the original source with the indexes of the rows in the table.

var originalIndex =$("#my-data-table tbody tr").context._DT_RowIndex;

var actualIndex = $("#my-data-table tbody tr").context.rowIndex;

So cool! : )

Spoiler

The next post will be about more customising and we will check on big change detailed. This change will be the rewriting of the DataTables pagination that is labeled by Allen with “Pagination plug-ins are (generally!) the most complicated of the plug-ins that DataTables allows.” We will go through it together and hopefully you will find it after reading the post not so difficult as it first looked like.

Make data table reusable

In the previous post we checked the project itself behind this blog post series and the library that we chose as a base of our implementation.

Our next task with the base library in our hand was to place the dataTables inside our Angular.js app. This post will be about the steps to achive this.

The best way to create a reusable component in angular is to create a directive. So I wrapped the dataTables implementation inside a directive.

Let’s see how!

The directive

angular.module('angularTableApp')
    .directive('myDataTable', function ($parse) {
        'use strict';

        return {
            template: '<table class="display table table-striped table-hover" id="my-data-table"></table>',

            link: function postLink(scope, element, attrs) {

			…

			$("#my-data-table").dataTable(...);
			…	

		}
	};
);

I will not explain all the settings of an angular directive in this post because I plan to write posts about angular.js and about directives as well.
( But if you have never heard about directives you can check the official page. )

There are parts that we need to know at this point:

  • We have an angular module, the main module with the name ‘angularTableApp’ and a directive is a reusable UI elment we can define
  • ‘myDataTable’ is the name of the directive → my-data-table will be the name of the directive in HTML
  • This directive will be available as an attribute of a HTML element.
  • If a div has our attribute, angular will build the content of the template inside the div.
  • In the ‘template’ I defined a HTML table with the Twitter Bootstrap classes and with the id to identify it. (Instead of id you can use class to have more table on one page.)
  • The div with the id ‘customHeader’ is a placeholder for the custom header controls.
  • In the ‘link’ function we are after the compile phase. We have all the DOM elements in place – the content of the template is inside the div – and we can manipulate them.

This ‘link’ function has three parameters

  • ‘scope’ – The scope from the controller
  • ‘element’ – The element on which the my-data-table attribute is
  • ‘attrs’ – The attrs gives us all the other attributes on the container div

The HTML part

We need to mark the HTML element with the attribute of the directive – ‘my-data-table’ – and we can add other attributes which we want to use later inside the directive:

<div my-data-table 
		my-source="dataSource" 
		refresh="refresh(data)" 
		header="headerControls" 				//optional
		page-sizes="pageSizes" 					//optional
        sorting-properties="sortingProperties"  //optional
        hiding-properties="hidingProperties" 	//optional
        delete-row="deleteRow(item)" 			//optional
        edit-row="editRow(item)" 				//optional
        select-row="select(item)" 				//optional
        >
</div>

‘my-source’ and ‘refresh’ are required parts of the data table. The others are only optional tags.

  • ‘my-source’ is the data source of the actual page of the table.
  • ‘refresh’ is the callback to react on the change of the table queries.

Inside the directive

On the script side, inside the directive’s link function we will use now only two from the paramaters.

We need attrs to to reach all the optional parameters of the container element and the scope to get the bound values this attributes from the actual scope of the page.

We can bind functions and properties of the actual scope as attributes of the HTML element.
On the directive’s side there are two different method to get the bound items correctly.
One to get the functions and one to get the properties.

How to handle bound functions inside the directive

We can get the name of the function from the attributes and after that we need to call the $parse function of angular with the name of the function.

var refreshHandler = $parse(attrs.refresh);

‘$parse’ can be requested from angular and get through dependency injection

angular.module('angularTableApp').directive('myDataTable', function ($parse) { … }

After parse you get the function itself and you can call it in an angular specified way.

scope.$apply(function () {

                        refreshHandler({data: conditions}, scope, { $event: event });
                    }); 

Tip 1 – Check noop
If you try to parse function by the attribute name but the user has not defined the attribute – in this case ‘refresh’ – it will not work.
It will give you an empty function with the name ‘noop’. (Angular built-in function.)
If it’s a ‘noop’ function and you call it like it would be your function, nothing will happen. It will not throw an exception but will not do what you request from it.

To avoid this malfunctioning you can check the result of the parse:

if (refreshHandler != angular.noop) {/*activate the option to call this and/or do the call*/}

Tip 2 – How to use parameters
If you want to add parameters to the function, they have to be defined in parse time.
Which means that in HTML you need to write refresh=”refresh(data)”.

In this case you can call the function later with the ‘data’ parameter.
If you write in html only refresh=”refresh()” it will not handle the parameter. It will not throw an exception. It will only ignore the parameter.

If you registered the parameter you can pass it to the function and use it in the controller.

Tip 3 – The way of function call
The from attribute parsed functions are outside the angular environment / angular loop so we can’t just call them as an original function of the angular scope.
We need to call them inside the apply method of the angular’s scope.

scope.$apply(function () {

                        refreshHandler({data: conditions}, scope, { $event: event });
                    }); 

Calling the function requires to pass the scope and to set the $event to it. (The way of Angular.js)

How to handle bound properties inside the directive

It is much easier because we have the scope of the controller. (Remember the scope is one of the parameters of our ‘link’ function.)

We only need to index the scope with the property name.

	scope[attrs.header]

The property name what we want to reach comes from the the other parameter of the function, from ‘attrs’.
That’s it!

Summary

These were the steps of the integration. Now we have the data table as a reusable component to use it inside an Angular.js app. We created the channel to communicate between our directive and the page’s scope.
We can hide the implementation of all the features that we want to add to our table inside this directive and we can publish to the user of our table a nice API.

In the next post we will check how to fill the table with features and I will show the implementation of the features from the sample app.

Data table in angular

A whole new extension from me! Download it, use it, rate it! Any feedback is welcomed!

banner

Have you ever found yourself in need of a fully customisable data table in an angular app?

No? – Than this is not the post you are looking for.

We needed one anyway.

So, first we started to look for a good data table implementation.
After some researching we found a pretty one, which is called DataTables.
It has many cool functions and it is well documented as well. But its best feature is that it is fully customisable. Exactly what we wanted!

How easy is the use the dataTable?
1. Create a <table> HTML tag with an id.
2. Write the following call inside your script.

$(document).ready(function() {
$('#theId').dataTable();
} );

3. Ready.

It looks cool and has a lot of built-in functionalities.

dataTables-original-table

So we had the base implementation but we had a lot of custom requirements for it:

  • We needed it as an angular.js directive.
  • We use Twitter Bootstrap to design our UI and to style the data table as well.
  • Custom header controls for many other filtering options.
  • Custom paging. We had the dodgiest requirement here.
    (To explain this it will be devoted a whole post.)
  • Custom row selection. Row selection and navigation with the arrow keys.
  • Custom controls for rows. Inline delete, edit options to rows.
  • Custom column hiding and sorting.

And we wanted all of this through a friendly API. How we succeeded?

Check the result!

The usage of my implementation in HTML:

&lt;div my-data-table
   my-source=&quot;dataSource&quot;
   refresh=&quot;refresh(data)&quot;
   header=&quot;headerControls&quot; //optional
   page-sizes=&quot;pageSizes&quot; //optional
   sorting-properties=&quot;sortingProperties&quot; //optional
   hiding-properties=&quot;hidingProperties&quot; //optional
   delete-row=&quot;deleteRow(item)&quot; //optional
   edit-row=&quot;editRow(item)&quot; //optional
   select-row=&quot;select(item)&quot; //optional
&gt;
&lt;/div&gt;

And the result looks like this:

dataTables-my-table

This sort blog post series will be about showing the main steps of the implementation.

My implementation with a small sample app is available on github. So you can check it out, download it and try it for yourself!

By reading the upcoming blog post you can get the big picture about the frameworks of the development. You can get the answers of the why?-s and how?-s.

Check the next post in this series!