Add number range filter to multiple DataTable columns

958 views Asked by At

I have a DataTable and I'm trying to add filters to multiple columns in it. Some columns are strings and require text inputs while others are numbers and require range inputs. I add filters to each column as described here. For the range inputs, I'm trying to add my own custom search plugin, as described here. Essentially, I'm trying to combine strategies from both parts of the documentation: I want to loop through each column applying filters, and for those columns that are numerical, I want to employ range filters. The example table they provide in their multi-column filtering documentation includes numerical columns, but the filters they use for those columns are text inputs, which, frankly, doesn't seem like the way most would go about it in real-world implementations. The documentation they provide for setting up numerical range filters includes an example that only has one column to be filtered by the input, which allows them to hard-code the appropriate column index into their custom function.

My problem is that I don't know how to get the variables that I need into the custom range filter function. Specifically, I need to get the column index and the user inputs into the function. I'm using this bootstrap slider for the range inputs, so in order to get the user input values, I call .slider on my input and pass in 'getValue'.

How do I get my variables, specifically the column index and the user inputs, into my custom range filter function?

My code is below. I also made this JSFiddle that shows what I'm trying to do. Notice how both text inputs work if you comment out the custom DataTables function.

function populateEntryTable() {
    $('#entryTableContainer').empty();
    /* put demo data in array of objects that is used to populate DataTable */
    var entries = [{name: John, age: 20, salary: 40000},
     {name: Bill, age: 40, salary: 200000},
     {name: Amy, age: 31, salary: 150000}];

    /*build my table*/
    $('#entryTableContainer').append('<table id="entryTable"><thead><tr></tr></thead><tbody></tbody></table>');
    for (var key in entries[0]) {
        $('#entryTableContainer thead tr').append('<th>' + key + '</th>');
    }
    for (var i = entries.length - 1; i >= 0; i--) {
        for (var key in entries[i]) {
            $('#entryTableContainer tbody tr:last-child').append('<td>' + entries[i][key] + '</td>');
        }
    }

    /* add column filters below each column, as described in DataTables documentation */
    $('#entryTable thead tr').clone(true).appendTo('#entryTable thead');
    var numberInputs = ['age','salary'];
    $('#entryTable thead tr:eq(1) th').each(function(i) {
        var title = $(this).text();
            /* if the col requires a text input filter, do text input filter stuff, which works fine. Else if it requires a number range filter, do number filter stuff, which doesn't work fine. */
            if (numberInputs.indexOf(title) == -1) {
              $(this).html('<input type="text" placeholder="Search">');
              $('input',this).on('keyup change',function() {
                if (entryTable.column(i).search() !== this.value) {
                    entryTable.column(i).search(this.value).draw();
                }
            });
        } else if (numberInputs.indexOf(title) > -1) {
        /* get min and max values in each column to set appropriate bootstrap-slider attributes */
            var min;
            var max;
            $('#entryTable tbody tr').each(function(j) {
                var item = parseFloat($('#entryTable tbody tr:eq(' + j + ') td:eq(' + i + ')').text());
                if (min == undefined || item < min) {
                    min = Math.floor(item);
                }
                if (max == undefined || item > max) {
                    max = Math.ceil(item);
                }
            });
        
        /* create bootstrap-slider with double inputs */
            $(this).html('<input id="' + title + '" data-slider-min="' + min + '" data-slider-max="' + max + '" data-slider-step="1" data-slider-value="[' + min + ',' + max + ']">');
            $('#' + title).slider({});
        
        /* add listener for bootstrap-slider change */
            $('input',this).on('change',function() {
            /* returns an array with the min and max user inputs*/
               var userInputs = $(this).slider('getValue');
               var userMin = userInputs[0];
               var userMax = userInputs[1];
               entryTable.draw();
            });
        }
    });

    /* call DataTable on my table and include my option settings*/
    var entryTable = $('#entryTable').DataTable({
        orderCellsTop: true,
        paging: false,
        bInfo: false,
        scrollY: 400,
        scrollCollapse: true,
        order: [ 1, 'desc' ],
        searching: true
    });

    /* searching must be set to true for my column searches to work, but I don't want the whole table search bar to display, so I remove it here */
    $('#entryTable_filter').addClass('d-none');
}

// custom DataTables function for filtering number ranges
$.fn.dataTable.ext.search.push(
    function( settings, data, dataIndex ) {
        /* how do I get i (the col index of the filter that the user is engaging with), the userMin and the userMax into here??? */
        var colVal = parseFloat(data[i].replace('$','')) || 0;
 
        if ( ( isNaN( userMin ) && isNaN( userMax ) ) ||
             ( isNaN( userMin ) && colVal <= userMax ) ||
             ( userMin <= colVal   && isNaN( userMax ) ) ||
             ( userMin <= colVal   && colVal <= userMax ) )
        {
            return true;
        }
        return false;
    }
);
1

There are 1 answers

0
orangeman51 On BEST ANSWER

I was able to solve my problem by moving the custom function inside the .each loop, bringing the variables I need into the proper scope. I put my solution on JSFiddle.

    $(function() {
  populateEntryTable()

  function populateEntryTable() {
    $('#entryTableContainer').empty();
    /* put demo data in array of objects that is used to populate DataTable */
    var entries = [{
        name: 'John',
        title: 'Coordinator',
        age: 20,
        salary: 40000
      },
      {
        name: 'Bill',
        title: 'Manager',
        age: 40,
        salary: 200000
      },
      {
        name: 'Amy',
        title: 'Manager',
        age: 31,
        salary: 150000
      }
    ];

    /*build my table*/
    $('#entryTableContainer').append('<table id="entryTable"><thead><tr></tr></thead><tbody></tbody></table>');
    for (var key in entries[0]) {
      $('#entryTable thead tr').append('<th>' + key + '</th>');
    }
    for (var i = entries.length - 1; i >= 0; i--) {
      $('#entryTable tbody').append('<tr></tr>');
      for (var key in entries[i]) {
        $('#entryTable tbody tr:last-child').append('<td>' + entries[i][key] + '</td>');
      }
    }

    $('#entryTable thead tr').clone(true).appendTo('#entryTable thead');
    var numberInputs = ['age', 'salary'];
    $('#entryTable thead tr:eq(1) th').each(function(i) {
      var title = $(this).text();
      // if the col requires a text input filter, do text input filter stuff, which works fine. Else if it requires a number range filter, do number filter stuff, which doesn't work fine.
      if (numberInputs.indexOf(title) == -1) {
        $(this).html('<input type="text" placeholder="Search">');
        $('input', this).on('keyup change', function() {
          if (entryTable.column(i).search() !== this.value) {
            entryTable.column(i).search(this.value).draw();
          }
        });
      } else if (numberInputs.indexOf(title) > -1) {
        // get min and max values in each column to set appropriate bootstrap-slider attributes
        var min;
        var max;
        $('#entryTable tbody tr').each(function(j) {
          var item = parseFloat($('#entryTable tbody tr:eq(' + j + ') td:eq(' + i + ')').text());
          if (min == undefined || item < min) {
            min = Math.floor(item);
          }
          if (max == undefined || item > max) {
            max = Math.ceil(item);
          }
        });

        var rangeMax = title == 'age' ? 100 : 1000000;
        var step = rangeMax == 100 ? 1 : 10000;
        $(this).html('<input id="' + title + '" type="range" value="0" min="0" max="' + rangeMax + '" step="' + step + '">');
        var userInput = $('input', this).val();

        // custom DataTables function for filtering number ranges
        $.fn.dataTable.ext.search.push(
          function(settings, data, dataIndex) {
            var colVal = parseFloat(data[i]) || 0;
            if (colVal > userInput) {
              return true;
            }
            return false;
          }
        );

        // add listener for bootstrap-slider change
        $('input', this).on('change', function() {
          userInput = $(this).val();
          entryTable.draw();
        });
      }
    });
    /* call DataTable on my table and include my option settings */
    var entryTable = $('#entryTable').DataTable({
      orderCellsTop: true,
      paging: false,
      bInfo: false,
      scrollY: 400,
      scrollCollapse: true,
      order: [1, 'desc'],
      searching: true
    });
    /*     hide whole table search bar--cannot set 'searching' to false because this also disables individual column search capabilities */
    $('#entryTable_filter').hide();
  }
});