Solved - Resetting column indexes for column filters on reorder

Solved - Resetting column indexes for column filters on reorder

Ironwil616Ironwil616 Posts: 50Questions: 0Answers: 0
edited October 2011 in General
Datatables is by far the coolest table plugin I've ever seen. For most implementations, it just works and provides a very robust interface. The real headaches only happen when trying to integrate several features at once.

The issue occurred when drag/dropping a column into a new position (but I also noted it when hiding columns with 'bVisible'). Column-specific filtering worked great, until you moved a column. Then, only anarchy remained. This just drove me nuts. I tried all manner of fixes to get the filters to use the current index of the column being filtered on, but nothing worked. What I came up with is just a quick fix that might interfere with other functionality, so test everything out if you use it. Ideally, at some point the filter handlers will be updated to be dynamic rather than cached. I read that this was to improve performance, but I can't think that retrieving the column index of the search filter currently being used would incur much overhead. When I was setting up initializers for the drop-down list filters, but didn't need one for each column, this did the trick:

[code]
function InitializeDropdownFilters(oTable)
{
$(".drop-down-filter").each(function (i)
{
var column = $(this);
var index = column.parent("tr").children().index(column);
$('select', this).change(function ()
{
oTable.fnFilter($(this).val(), index);
});
});
}
[/code]

It's almost exactly like the code in the example pages, but it grabs the index of each drop down list (which each have a class of 'drop-down-filter', oddly enough) and assigns that instead of using the loop's index variable. I constructed the drop down lists myself with data passed in, rather than grabbing column data, but that's neither here nor there. The point is that grabbing the current index only took one line of code.

So, I've seen this kind of question come up often, but hadn't found a good solution yet. The problem seems to be that datatables caches index/column mappings when initialized, and never refreshes them. Since you can't reinitialize a table (read that in the forums), this is a real problem. When stepping through the code to identify and hopefully reassign the column index being used, I ended up at the _fnGetCellData function.The correct column index to filter on is sent in. Here's the relevant part of that function:

[code]
var sData;
var oCol = oSettings.aoColumns[iCol];
var oData = oSettings.aoData[iRow]._aData;

if ((sData = oCol.fnGetData(oData)) === undefined)
{
if (oSettings.iDrawError != oSettings.iDraw && oCol.sDefaultContent === null)
{
_fnLog(oSettings, 0, "Requested unknown parameter '" + oCol.mDataProp +
"' from the data source for row " + iRow);
oSettings.iDrawError = oSettings.iDraw;
}
return oCol.sDefaultContent;
}
[/code]

The 'oCol' variable is populated with the correct column, and has the following properties set:

oCol._ColReorder_iOrigCol: 2
iDataSort: 3
mDataProp: 2

So it's clear that the column is extremely aware that it's been moved, and where its current index is. The next line just grabs the data for the current row being processed, but the line after that is the problem:

[code]
if ((sData = oCol.fnGetData(oData)) === undefined)
[/code]

It's assigning a value to 'sData' here, which is the value of the cell index being retrieved. Unfortunately, this does not use the 'iCol' param being sent in, which was very surprising to me. To support dynamic changes to the table, it seems logical to decide which column index to use when it's being used, instead of grabbing it from a cached source. I stepped into that code, and was jumped to the bottom of the function assigned to 'oCol.fnGetData', which is '_fnGetObjectDataFn'. Here is the part I find very confusing. The call to this function is:

[code]
oCol.fnGetData(oData))
[/code]

Remember, 'oData' is currently an array of the cell values for the current row. However, when I step into that function, whose definition looks like this:

[code]
function _fnGetObjectDataFn(mSource)
[/code]

'mSource' is an index, not the array of row values, and it is not the index of the column in its current position. It's the old index, which is assigned and cached someplace that I couldn't find. My JS-Fu wasn't that strong. What would be a better solution, IMO, would be to use the column's current 'iDataSort' value for filtering by specific column. This can't just be swapped out for 'mDataProp' at initialization (I tried), because it has the same effect as using 'mDataProp'. Since they're the same at initialization, it would just be cached like the other and yield the same breakage between the column's current position and which column it's used to filter on. I tried adding a parameter to '_fnGetObjectDataFn', and use that instead of the 'mSource' param, but this caused init errors. Finally, I just replaced:

[code]
if ((sData = oCol.fnGetData(oData)) === undefined)
[/code]

with:

[code]
if ((sData = oData[iCol]) === undefined)
[/code]

Poof! Problem solved. I just side-stepped the cached filter functions entirely. I'm quite certain this might break some other functionality, but so far I haven't seen anything go wrong. I've tested this with text input filters and the global table filter, and they all work just fine. Now I can drag/drop until the cows come home, and the filters use my current index instead of a cached one.

Replies

  • Ironwil616Ironwil616 Posts: 50Questions: 0Answers: 0
    edited October 2011
    A quick add-in - unless you plan to add in a text input field for every column in your table, you'll have to wire up something to grab the index of the ones you're using. Above I showed what worked for me with drop-down lists. Here's my text input filter init code:

    [code]
    $("tfoot input").each( function (i) {
    var th = $(this).parent();
    var tr = $(this).parents("tr");
    var index = tr.children().index(th);

    $(this).keyup( function () {
    /* Filter on the column (the index) of this element */
    oTable.fnFilter( this.value, index );
    } );
    } );
    [/code]

    I broke it down like that because I was having issues getting the index properly and I'm in a hurry. Now I can move my columns anywhere in the table without having to place an input filter in each 'th' element. Another approach would have been to add one in to each column and set its display to none, but I like this approach better.
This discussion has been closed.