Friday, 19 August 2016

DataTables Search for, and order by, select value

There was a cracking question on stackoverflow the day before yesterday and it got me scratching my head.

It was to do with filtering and sorting rows when there was a select input in the row and the selected option was what was being searched or ordered by. DelightedD0D had the ordering down pat but couldn't do the search. I spent far too long thinking about an answer but my solution broke the universal search within DataTables themselves and I wanted to just add to the search not replace it. This was my first attempt:

$.fn.dataTable.ext.search.push(
    function(settings, data, dataIndex) {
        var dataLabel = table
            .row(dataIndex) //get the row to evaluate    
            .nodes()        //extract the HTML - node() does not support to$     
            .to$()          //get as jQuery object 
            .find('select') //find column with the select input
            .val();         //get the value of the select input
        // return true or false if the val matches the search parameter
        return !!~dataLabel.toLowerCase().indexOf(table.search().toLowerCase()); 
    }     
);

I felt sure that there must be a better way fo doing it though and finally I've found it, it depends upon the column having a type of "selected" so that given the following table markup:

<table id="example">
    <thead>
        <tr>
            <th>
                First name
            </th>
            <th>
                Last name
            </th>
            <th>
                Position
            </th>
            <th>
                Office
            </th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>
                Tiger
            </td>
            <td>
                Nixon
            </td>
            <td>
                <select>
                    <option 
                      value="">
                        Please choose
                    </option>
                    <option 
                      value="System Architect" 
                      selected="selected">
                        System Architect
                    </option>
                    <option 
                      value="Accountant">
                        Accountant
                    </option>
                    <option 
                      value="Senior Javascript Developer">
                        Senior Javascript Developer
                    </option>
                    <option 
                      value="Junior Technical Author">
                        Junior Technical Author
                    </option>
                </select>
            </td>
            <td>
                Edinburgh
            </td>
        </tr>
        <!-- more rows here -->
    </tbody>
</table>

This code will allow for ordering and filtering on the value of the select input:

(function() {
    $.fn.dataTable.ext.type.search.selected = (data) => !$(data).is("select") 
     ? '' 
        : $(data).val();
    $.fn.dataTable.ext.order['dom-select'] = function(settings, col) {
        return this.api().column(col, {
            order: 'index'
        }).nodes().map(td => $('select', td).val());
    }
})();
var table = $('#example').DataTable({
    "columnDefs": [{
        "orderDataType": "dom-select",
        "type": "selected",
        "targets": 2
    }]
});
$("#example select").on("change", function() {
    var $this = $(this),
        val = $this.val(),
        cellPosition = table.cell($this.parents("td")).index(),
        rowDate = table.row(cellPosition.row).data();
    $this.find("option").each((k, v) => ($(v).val() === val) 
     ? $(v).attr("selected", "selected") 
        : $(v).removeAttr("selected"));
    rowDate[cellPosition.column] = $this.prop("outerHTML");
    table.row(cellPosition.row).data(rowDate).draw();
});

The special stuff is in the change event of the select input as it updates the data for the row and then redraws the table, allowing the search extension to know that the table has changed.