Friday 31st March, 2017

Ajax loaded row details

Showing detailed information about a row in a DataTable is a popular feature. It allows the main table view to be accessible and easy for the end user to process, while also allowing them to drill into more detailed data in a structured way.

DataTables provides child rows API methods (row().child(), row().child().show(), etc) to let you easily display detail rows such as these. There is even a simple example on this site.

The topic for this post is to build upon that example, describing how to handle a simple, but commonly requested, extension to it: to load the data for the child row asynchronously via Ajax rather than simply using the data already in the table.

Example

The table below shows an example of what we will build in this article. Clicking on the show icon will display a "Loading" message, which is then replaced by data that has been Ajax loaded from the server. In this demo an artificial two second delay has been added to the server-side script so you can see the loading message and that then being replaced by the loaded information.

Name Position Office Salary
Name Position Office Salary

Setup

The majority of this post is actually about the basic setup of the DataTable as the final implementation is perhaps surprisingly trivial. If you are already comfortable creating a DataTable, skip ahead to the "Ajax Request" section.

Basic DataTable

Let's start right at the beginning and create a DataTable with Ajax sourced data that uses objects as the data source

var table = $('#myTable').DataTable( {
    ajax: '/api/staff',
    columns: [
        {
            className:      'details-control',
            orderable:      false,
            data:           null,
            defaultContent: ''
        },
        { data: "name" },
        { data: "position" },
        { data: "office" },
        { data: "salary" }
    ],
    order: [[1, 'asc']]
} );

Note that the first column in the table has a class assigned to it and simply shows an empty string (i.e. nothing!) as its data value. This is so we can use that cell for an open / close icon, letting the end user click upon it to show the details row.

Event handlers

We can attach the following event listener to perform the open / close action:

$('#myTable tbody').on('click', 'td.details-control', function () {
    var tr = $(this).closest('tr');
    var row = table.row( tr );

    if ( row.child.isShown() ) {
        row.child.hide();
        tr.removeClass('shown');
    }
    else {
        row.child( format(row.data()) ).show();
        tr.addClass('shown');
    }
} );

In the code above we use row().child.isShown() to check if the row has a child row shown or not already. If so, then hide it with row().child.hide() and if not create it using row().child().show().

It is this later function that this entire article revolves around! In its simplest case you can pass a string to row().child() and it will show that string in the child row, but importantly, you can also pass a node (or a jQuery object containing a node) and that will be displayed in the table. This makes it very easy to update the document once the Ajax request has been completed as long as we retain a reference to that node.

Ajax request

Our goal is to create an Ajax request, the response of which will determine what is shown in the details row. While the data is being loaded we want to show a "Loading" message to the end user, so they are aware that something is happening. We can do this by using an exceptional powerful feature of Javascript, one which we often use as Javascript developers, but don't always assign a name to: closures.

You'll note on line 10 of the event handler code we call a custom defined function called format (use whatever function name you want!) and pass in the row data - what we want to do is have it return an element with the loading message to DataTables so that is immediately displayed, but at the same time create an Ajax request that will update that element once the data has loaded.

function format ( rowData ) {
    var div = $('<div/>')
        .addClass( 'loading' )
        .text( 'Loading...' );

    $.ajax( {
        url: '/api/staff/details',
        data: {
            name: rowData.name
        },
        dataType: 'json',
        success: function ( json ) {
            div
                .html( json.html )
                .removeClass( 'loading' );
        } 
    } );

    return div;
}

That's basically it! What is shown in the child row is entirely up to yourself since its just a div container element. You can use animation to show it, insert another DataTable, whatever you like! The key here is returning the div to be rendered by the child row, while also using Ajax and a closure to populate that div asynchronously.