Sunday 19th June, 2011

Drill-down rows

Update: This blog post was written before the release of DataTables 1.10, and therefore does not make use of the new API or features used by the latest DataTables releases. It also uses a legacy version of jQuery and some parts might need to be updated if you wish to use this code.

Tables are the perfect solution to show summary data which can be readily tabulated. However, often you will want to provide the end user of your web-site / app with the ability to drill down into the data in the table and show more detailed information. This can help clean up the interface and stop the end user being overwhelmed by information and give them more control.

DataTables provides two API methods to attach a details row to a parent in the table; fnOpen to show the extra data and its reciprocal fnClose to remove it. The data which is shown in the details row can be more or less whatever you wish as fnOpen will accept a DOM node or HTML string. In this example I'll show an inner table being used which is animated open and closed.

This example also shows the new mDataProp option included in DataTables 1.8 in action. How you the data for the details row is obtained can be done in a number of different ways, including Ajax or a DOM source. In this example through the use of mDataProp and DataTables consuming a JSON object for the table display, we can use the original object that is given to the table, showing information that is not in the default view of the table.

Rendering engine Browser CSS grade

The table

As per usual I'll use my standard set to show in the table, but in this case only three of the five columns will be shown by default - the remaining information will be shown in the details display. Note also that there is a spare column at the start of the table which is used for the show / hide details row that the end user can click on to control the display.

<table cellpadding="0" cellspacing="0" border="0" class="display" id="example">
    <thead>
        <tr>
            <th></th>
            <th>Rendering engine</th>
            <th>Browser</th>
            <th>CSS grade</th>
        </tr>
    </thead>
    <tbody></tbody>
</table>

The data source looks like this:

{ "aaData": [
    {
        "engine": "Trident",
        "browser": "Internet Explorer 4.0",
        "platform": "Win 95+",
        "version": "4",
        "grade": "X"
    },
    {
        "engine": "Trident",
        "browser": "Internet Explorer 5.0",
        "platform": "Win 95+",
        "version": "5",
        "grade": "C"
    },
    ...
] }

And the DataTables initialisation is fairly simple with the use of mDataProp. Note that the first column has an mDataProp value of null, and sDefaultContent is given to have DataTables render a static string.

$(document).ready(function() {
  var anOpen = [];
    var sImageUrl = "/release-datatables/examples/examples_support/";

    var oTable = $('#example').dataTable( {
        "bProcessing": true,
        "sAjaxSource": "/release-datatables/examples/ajax/sources/objects.txt",
        "aoColumns": [
            { 
               "mDataProp": null, 
               "sClass": "control center", 
               "sDefaultContent": '<img src="'+sImageUrl+'details_open.png'+'">'
            },
            { "mDataProp": "engine" },
            { "mDataProp": "browser" },
            { "mDataProp": "grade" }
        ]
    } );
} );

View control

Now that we've got the table being displayed we need to add an event handler to the control button, to call fnOpen and fnClose as required. Here we will keep a reference to any TR rows that we 'open' in the array anOpen - in this manner we can use $.inArray to see if the row should be opened or if it is already open and thus to close it. We also update the control image to show a 'close' button to give the end user extra information about what the controls will do which clicked on.

Note also the function fnFormatDetails which is used to build up the string which is given to fnOpen for display. We use fnGetData to obtain the original data object for the row to get the information for the display (as noted before you can get this data any way you wish).

$('#example td.control').live( 'click', function () {
   var nTr = this.parentNode;
   var i = $.inArray( nTr, anOpen );

   if ( i === -1 ) {
      $('img', this).attr( 'src', sImageUrl+"details_close.png" );
      oTable.fnOpen( nTr, fnFormatDetails(oTable, nTr), 'details' );
      anOpen.push( nTr );
    }
    else {
      $('img', this).attr( 'src', sImageUrl+"details_open.png" );
      oTable.fnClose( nTr );
      anOpen.splice( i, 1 );
    }
} );

function fnFormatDetails( oTable, nTr )
{
  var oData = oTable.fnGetData( nTr );
  var sOut = 
    '<div class="innerDetails">'+
      '<table cellpadding="5" cellspacing="0" border="0" style="padding-left:50px;">'+
        '<tr><td>Rendering engine:</td><td>'+oData.engine+'</td></tr>'+
        '<tr><td>Browser:</td><td>'+oData.browser+'</td></tr>'+
        '<tr><td>Platform:</td><td>'+oData.platform+'</td></tr>'+
        '<tr><td>Version:</td><td>'+oData.version+'</td></tr>'+
        '<tr><td>Grade:</td><td>'+oData.grade+'</td></tr>'+
      '</table>'+
    '</div>';
  return sOut;
}

Animation

So that's the table fully functional - but let's add in a little animation to make it smoother for the end user. The jQuery functions $().slideDown and $().slideUp are used for the animation. Note that in fnFormatDetails I've put a wrapper element with a class of "innerDetails" which is the element we will animate. fnOpen returns a reference to the TR node which is appended to the table for the details row, which we use as part of the selector to get the element needed.

The updated control code is shown below:

$('#example td.control').live( 'click', function () {
  var nTr = this.parentNode;
  var i = $.inArray( nTr, anOpen );

  if ( i === -1 ) {
    $('img', this).attr( 'src', sImageUrl+"details_close.png" );
    var nDetailsRow = oTable.fnOpen( nTr, fnFormatDetails(oTable, nTr), 'details' );
    $('div.innerDetails', nDetailsRow).slideDown();
    anOpen.push( nTr );
  }
  else {
    $('img', this).attr( 'src', sImageUrl+"details_open.png" );
    $('div.innerDetails', $(nTr).next()[0]).slideUp( function () {
      oTable.fnClose( nTr );
      anOpen.splice( i, 1 );
    } );
  }
} );

Finally, to get the animation to work correctly, we need to hide the information wrapper element by default and let jQuery handle its display:

div.innerDetails { display: none }

Conclusion

Showing drill-down data using fnOpen can be a very useful interaction with a table, and is highly customisable as shown in this example. The triggering of the display, what is shown in the display and data source for the information can all be tightly integrated into your setup.

As usual there is a thread in the forum for comments and discussion on this post.