Monday, August 13, 2012

Tip on Combining Column Filtering and Column Hiding with jQuery DataTables Plugin

I'm a big fan of the jQuery plugin DataTables.  I use it in a number of projects to enhance HTML tables, making them sortable and searchable.

DataTables provides a number of advanced options and functions that let you customize the table's functionality.  One of these functions is fnFilter(), which you can use to filter the table contents based on the presence of a value in a particular column.  The most common use case for this function is to add text inputs to the header or footer of each column, and bind the use of the function to the keyup event, as in the official DataTables example:

$("tfoot input").keyup( function () {
    /* Filter on the column (the index) of this element */
    oTable.fnFilter( this.value, $("tfoot input").index(this) );
} );

 

...The "oTable" is a reference to the DataTables-enhanced table, the first parameter is the value of the input box in the footer, and the second parameter is the index value (position) of the column in the table.

DataTables also provides the fnSetColumnVis() function for showing/hiding columns, which is helpful if you have a table with a lot of columns and want to let the user get rid of the columns they don't need at that moment.  It takes two parameters, the index position of the column you want to hide and true or false to set the visibility state of the column:

oTable.fnSetColumnVis( 1, false );

 

Both are very useful functions, but you have to be careful when you enable both column filtering and column hiding on a single table. Hiding a column takes it out of the DOM, and therefore it changes the index position of every column to the right of the hidden one. While that makes sense, the problem is that DataTables keeps internal track of the original DOM positions and will execute the filter against the column with that original index value.

So say you have a table with columns A, B, and C going left to right.  DOM index positions start with 0, so column A's position is 0, column B's is 1, etc..  If you hide column A, then column B now has index position 0 and column C has position 1.  When you try and filter column C, fnFilter gets the current position of column C (1) and runs the filter against the column that originally had index position 1...which is column B.  So the table gets filtered based on column B even though the user entered their filter term in the footer of column C, which is obviously not what you want.

You can work around this issue by recording the initial index position of each column in an attribute in the input tag, and use that value as the second parameter for fnFilter().  So if you used an attribute like "colPos":

...
    <tfoot>
        <tr>
            <th><input type="text" value=" colPos="0"/></th>
            <th><input type="text" value=" colPos="1"/></th>
            ...
         </tr>
    </tfoot>
...

 

...then you'd rewrite your keyup bind function like so:

$("tfoot input").keyup( function () {
    /* Filter on the column (the index) of this element */
    oTable.fnFilter( this.value, $(this).attr("colPos") );
} );

 

That will keep the column filters tied to their original columns.