Thursday 2nd October, 2014

Sliding child rows

The ability to show information that relates to a specific record in a table can greatly simplify the primary display of a table for the end user, while still allowing access to arbitrarily complex data in the detail view. For this DataTables has built in support for showing child rows that are attached to a parent row in a table through its row().child() set of API methods.

An example is available that shows how these methods can be used to display information in a child row, but a frequent request is to add a little more style to the display of the child row by sliding it open with a bit of jQuery animation.

This post explores how we can very easily achieve that effect and the end result is shown in the table below.

Name Position Office Salary
Name Position Office Salary

Starting point

Our starting point will be the standard DataTables child row example. Let's quickly summarise how that works by considering the following code:

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

    if ( row.child.isShown() ) {
        // This row is already open - close it
        row.child.hide();
        tr.removeClass('shown');
    }
    else {
        // Open this row
        row.child( format(row.data()) ).show();
        tr.addClass('shown');
    }
} );
  • Line 1: Attach a click event listener to cells which have the class details-control in the table (set using the columns.className initialisation option.
  • Line 2: Use jQuery to obtain a reference to the tr node for this row
  • Line 3: Using the tr element we use the DataTables API to obtain an API object for the row that can then be used for the child row manipulation.
  • Line 5: Check if the child row is displayed or not using row().child.isShown()
  • Lines 6-8: If current visible, hide it using row().child.hide()
  • Lines 11-13: If not visible, show it using row().child.show() and a formatting function that will render the data from the row (using row().data()).

You will be able to see that the format() method used here is just a demonstration of what can be done and it can return any HTML required for display, regardless of how complex it is, based on your own needs. Indeed, it could make an Ajax call to a server to obtain addition information if required.

Adding animation

To add animation the first point to consider is that animating the height of table rows can be fairly complex, so we are going to side-step that entirely by having the data to be shown in the child row enclosed by a div element which can easily be animated using jQuery's $().slideDown() and $().slideUp() methods.

In the above example the format() function would return an HTML string such as:

<div class="slider">
    ... Data to be shown ...
</div>

In our CSS we add:

div.slider {
    display: none;
}

This ensures that the element is hidden when first added to the document so jQuery can animate the display of it.

Display

Now we simply need to use $().slideDown() to animate the element so modify the display of the child row by adding a single line to look like:

// Open this row
row.child( format(row.data()) ).show();
tr.addClass('shown');

$('div.slider', row.child()).slideDown();

Note the use of row().child() to obtain a reference to the child row node so we can select the element to be animated.

Hiding

The alteration to animate the child row on close is equally as simple - we use $().slideUp() and its ability to provide a callback to be executed on completion of the animation (the point at which we now what to use row().child.hide() to remove the child row completely rather than before or during the animation):

// This row is already open - close it
$('div.slider', row.child()).slideUp( function () {
    row.child.hide();
    tr.removeClass('shown');
} );

Smoother animation

When using the default DataTables stylesheet, and many other styling options, the cells in the table have padding. This padding is not part of the animation that jQuery performs on the div element, so we want to remove that for the child row rather than happing it immediately appear when the child row is shown. This can be done with a class name attached to the child row container cell, which can be assigned using the optional second parameter of the row().child() method:

// Open this row
row.child( format(row.data()), 'no-padding' ).show();

Add an extra CSS rule:

table.dataTable tbody td.no-padding {
    padding: 0;
}

And that is all there is to adding sliding animation to the child rows in DataTales!

Complete code

The complete code used for this example is shown below.

Javascript

/* Formatting function for row details - modify as you need */
function format ( d ) {
    // `d` is the original data object for the row
    return '<div class="slider">'+
        '<table cellpadding="5" cellspacing="0" border="0" style="padding-left:50px;">'+
            '<tr>'+
                '<td>Full name:</td>'+
                '<td>'+d.name+'</td>'+
            '</tr>'+
            '<tr>'+
                '<td>Extension number:</td>'+
                '<td>'+d.extn+'</td>'+
            '</tr>'+
            '<tr>'+
                '<td>Extra info:</td>'+
                '<td>And any further details here (images etc)...</td>'+
            '</tr>'+
        '</table>'+
    '</div>';
}

$(document).ready(function() {
    var table = $('#example').DataTable( {
        "ajax": "/examples/ajax/data/objects.txt",
        "columns": [
            {
                "class":          'details-control',
                "orderable":      false,
                "data":           null,
                "defaultContent": ''
            },
            { "data": "name" },
            { "data": "position" },
            { "data": "office" },
            { "data": "salary" }
        ],
        "order": [[1, 'asc']]
    } );
    
    // Add event listener for opening and closing details
    $('#example tbody').on('click', 'td.details-control', function () {
        var tr = $(this).closest('tr');
        var row = table.row( tr );

        if ( row.child.isShown() ) {
            // This row is already open - close it
            $('div.slider', row.child()).slideUp( function () {
                row.child.hide();
                tr.removeClass('shown');
            } );
        }
        else {
            // Open this row
            row.child( format(row.data()), 'no-padding' ).show();
            tr.addClass('shown');

            $('div.slider', row.child()).slideDown();
        }
    } );
} );

CSS

td.details-control {
    background: url('/examples/resources/details_open.png') no-repeat center center;
    cursor: pointer;
}

tr.shown td.details-control {
    background: url('/examples/resources/details_close.png') no-repeat center center;
}

div.slider {
    display: none;
}

table.dataTable tbody td.no-padding {
    padding: 0;
}