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.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s