0
votes

How can I exempt a single row in a DataTables.js table from DataTables' builtin filtering, so thta it is always shown?

Background: I'm building a table editing component using the jQuery-based DataTables.js library. Instead of using dialogs or overlays, I wanted to present editing controls right within the datatable, like this:

enter image description here

This works like a charm, even with active filters: I keep the original, unchanged data in the record while it is being edited, so I can use that data for the 'sort' and 'filter' modes of mDataProp, and my row stays in place and visible until editing is finished.

A bigger problem arises when I add a new row: There is no data to use for filtering, so if a filter is active, my row won't be visible. This breaks the workflow where the user searches through the dataset, sees that some record is missing, and (without clearing the filter) presses the "Add" button, waiting for an empty row with edit controls to appear:

enter image description here

How can I exempt this special row from DataTables' filtering?

1

1 Answers

0
votes

After reading through the source code of DataTables.js for some time, I came to the conclusion that there is no way to hook into the filtering in the desired way. There are hooks for custom filters, but they can only be used to hide stuff, not to show stuff.

However, there's a 'filter' event which is triggered after filtering, but before the table is rendered. My solution installs an handler for this event:

$('table#mydatatable').bind('filter', function() {
    var nTable    = $(this).dataTable();
    var oSettings = nTable.fnSettings();

    //collect the row IDs of all unsaved rows
    var aiUnsavedRowIDs = $.grep(oSettings.aiDisplayMaster, function(iRowID) {
        var oRowData = nTable.fnGetData(iRowID);
        return is_unsaved(oRowData);
    });
    //prepare lookup table
    var oUnsavedRecordIDs = {};
    $.each(aiUnsavedRowIDs, function(idx, iRowID) {
        oUnsavedRecordIDs[iRowID] = true;
    });

    //remove unsaved rows from display (to avoid duplicates after the
    //following step)
    for (var i = oSettings.aiDisplay.length; i >= 0; i--) {
        //iterate backwards, because otherwise, removal from aiDisplay
        //would mess up the iteration
        if (oUnsavedRecordIDs[ oSettings.aiDisplay[i] ]) {
            oSettings.aiDisplay.splice(i, 1);
        }
    }

    //insert unsaved records at the top of the display
    Array.prototype.unshift.apply(oSettings.aiDisplay, aiUnsavedRowIDs);
    //NOTE: cannot just say oSettings.aiDisplay.unshift(aiUnsavedRowIDs)
    //because this would add the array aiUnsavedRowIDs as an element to
    //aiDisplay, not its contents.
});

What happens here? First, I find all unsaved rows by looking through oSettings.aiDisplayMaster. This array references all rows that are in this DataTable, in the correct sorting order. The elements of aiDisplayMaster are integer indices into DataTables' internal data storage (one index per row).

The filtering process goes through the rows in aiDisplayMaster, and places the row IDs of all matching rows in oSettings.aiDisplay. This array controls which rows will be rendered (after this event handler has finished!). The whole process looks like this:

[1, ..., numRows]
    |
    |  sorting
    v
oSettings.aiDisplayMaster
    |
    |  filtering
    v
oSettings.aiDisplay
    |
    |  rendering
    v
   DOM

So after having located all unsaved records in aiDisplayMaster (using custom logic that I wrapped in an is_unsaved() function for the sake of this snippet), I add them all to aiDisplay (after removing existing instances of these rows, to avoid duplicates).

A side-effect of this particular implementation is that all unsaved rows appear at the top of the table, but in my case, this is actually desirable.