How to filter a very large bootstrap table using pure Javascript

前端 未结 7 2157
清歌不尽
清歌不尽 2021-02-05 12:05

I\'ve built a large table in bootstrap, about 5,000 rows x 10 columns, and I need to filter the table for specific attributes, fast, using only JavaScript. The table has both an

7条回答
  •  情书的邮戳
    2021-02-05 12:44

    I whipped up a filtering solution that you might want to check out.

    Features

    • can process a 5000 row table almost instantly*
    • uses plain old JavaScript; no need for libraries
    • no new syntax to learn; using it is as easy as calling a function
    • works fine with your preexisting table; no need to start from scratch
    • no data structures or caching required
    • supports multiple values per filter and multiple filters
    • supports inclusive and exclusive filtering
    • works just as well on a table that's detached from the DOM if you want to apply filters before displaying it.

    How it works

    The JavaScript is very simple. All it does is create a unique class name for each filter and add it to every row that matches the filter parameters. The class names can be used to determine which rows a given filter is currently filtering, so there's no need to store that information in a data structure. The classes share a common prefix, so they can all be targeted by the same CSS selector for applying the display: none declaration. Removing a filter is as simple as removing its associated class name from the rows that have it.


    The Code

    If you want to show only rows that have a value of "X" or "Y" in column 2, the function call would look something like this:

    addFilter(yourTable, 2, ['X','Y']);
    

    That's all there is to it! Instructions on removing a filter can be found in the demo code below.


    Demo

    The demo in the code snippet below allows you to apply any number of filters with any number of values to a 5000 row table like the one the OP described, and remove them afterward. It may look like a lot of code, but most of it is just for setting up the demo interface. If you were to use this solution in your own code, you'd probably just copy over the first two js functions (addFilter and removeFilter), and the first CSS rule (the one with display: none).

    /*
    The addFilter function is ready to use and should work with any table. You just need
    to pass it the following arguments:
      1) a reference to the table
      2) the numeric index of the column to search
      3) an array of values to search for
    Optionally, you can pass it a boolean value as the 4th argument; if true, the filter
    will hide rows that DO contain the specified values rather than those that don't (it
    does the latter by default). The return value is an integer that serves as a unique
    identifier for the filter. You'll need to save this value if you want to remove the
    filter later.
    */
    function addFilter(table, column, values, exclusive) {
      if(!table.hasAttribute('data-filtercount')) {
        table.setAttribute('data-filtercount', 1);
        table.setAttribute('data-filterid', 0);
        var filterId = 0;
      }
      else {
        var
          filterCount = parseInt(table.getAttribute('data-filtercount')) + 1,
          filterId = filterCount === 1 ?
            0 : parseInt(table.getAttribute('data-filterid')) + 1;
        table.setAttribute('data-filtercount', filterCount);
        table.setAttribute('data-filterid', filterId);
      }
      exclusive = !!exclusive;
      var
        filterClass = 'filt_' + filterId,
        tableParent = table.parentNode,
        tableSibling = table.nextSibling,
        rows = table.rows,
        rowCount = rows.length,
        r = table.tBodies[0].rows[0].rowIndex;
      if(tableParent)
        tableParent.removeChild(table);
      for(; r < rowCount; r++) {
        if((values.indexOf(rows[r].cells[column].textContent.trim()) !== -1) === exclusive)
          rows[r].classList.add(filterClass);
      }
      if(tableParent)
        tableParent.insertBefore(table, tableSibling);
      return filterId;
    }
    
    /*
    The removeFilter function takes two arguments:
      1) a reference to the table that has the filter you want to remove
      2) the filter's ID number (i.e. the value that the addFilter function returned)
    */
    function removeFilter(table, filterId) {
      var
        filterClass = 'filt_' + filterId,
        tableParent = table.parentNode,
        tableSibling = table.nextSibling,
        lastId = table.getAttribute('data-filterid'),
        rows = table.querySelectorAll('.' + filterClass),
        r = rows.length;
      if(tableParent)
        tableParent.removeChild(table);
      for(; r--; rows[r].classList.remove(filterClass));
      table.setAttribute(
        'data-filtercount',
        parseInt(table.getAttribute('data-filtercount')) - 1
      );
      if(filterId == lastId)
        table.setAttribute('data-filterid', parseInt(filterId) - 1);
      if(tableParent)
        tableParent.insertBefore(table, tableSibling);
    }
    
    /*
    THE REMAINING JS CODE JUST SETS UP THE DEMO AND IS NOT PART OF THE SOLUTION, though it
    does provide a simple example of how to connect the above functions to an interface.
    */
    /* Initialize interface. */
    (function() {
      var
        table = document.getElementById('hugeTable'),
        addFilt = function() {
          var
            exclusive = document.getElementById('filterType').value === '0' ? true : false,
            colSelect = document.getElementById('filterColumn'),
            valInputs = document.getElementsByName('filterValue'),
            filters = document.getElementById('filters'),
            column = colSelect.value,
            values = [],
            i = valInputs.length;
          for(; i--;) {
            if(valInputs[i].value.length) {
              values[i] = valInputs[i].value;
              valInputs[i].value = '';
            }
          }
          filters.children[0].insertAdjacentHTML(
            'afterend',
            '
    ' + colSelect.options[colSelect.selectedIndex].textContent.trim() + (exclusive ? '; [' : '; everything but [') + values.toString() + ']
    ' ); var filter = filters.children[1], filterId = addFilter(table, column, values, exclusive); filter.children[0].addEventListener('click', function() { filter.parentNode.removeChild(filter); removeFilter(table, filterId); }); }, addFiltVal = function() { var input = document.querySelector('[name="filterValue"]'); input.insertAdjacentHTML( 'beforebegin', '' ); input.previousElementSibling.focus(); }, remFiltVal = function() { var input = document.querySelector('[name="filterValue"]'); if(input.nextElementSibling.name === 'filterValue') input.parentNode.removeChild(input); }; document.getElementById('addFilterValue').addEventListener('click', addFiltVal); document.getElementById('removeFilterValue').addEventListener('click', remFiltVal); document.getElementById('addFilter').addEventListener('click', addFilt); })(); /* Fill test table with 5000 rows of random data. */ (function() { var tbl = document.getElementById('hugeTable'), num = 5000, dat = [ 'a','b','c','d','e','f','g','h','i','j','k','l','m', 'n','o','p','q','r','s','t','u','v','w','x','y','z' ], len = dat.length, flr = Math.floor, rnd = Math.random, bod = tbl.tBodies[0], sib = bod.nextSibling, r = 0; tbl.removeChild(bod); for(; r < num; r++) { bod.insertAdjacentHTML( 'beforeend', '' + r + '' + dat[flr(rnd() * len)] + ''); } tbl.insertBefore(bod, sib); })();
    [class*="filt_"] {display: none;} /* THIS RULE IS REQUIRED FOR THE FILTERS TO WORK!!! */
    
    /* THE REMAINING CSS IS JUST FOR THE DEMO INTERFACE AND IS NOT PART OF THE SOLUTION. */
    h3 {margin: 0 0 .25em 0;}
    [name="filterValue"] {width: 2.5em;}
    [class*="filt_"] {display: none;}
    #addFilter {margin-top: .5em;}
    #filters {margin-left: .5em;}
    #filters > div {margin-bottom: .5em;}
    #filters > div > input, select {margin-right: .5em;}
    #filters, #hugeTable {
      float: left;
      border: 1px solid black;
      padding: 0 .5em 0 .5em;
      white-space: nowrap;
    }
    #hugeTable {border-spacing: 0;}
    #hugeTable > thead > tr > th {
      padding-top: 0;
      text-align: left;
    }
    #hugeTable > colgroup > col:first-child {min-width: 4em;}

    Add Filter

    Column: Action: Value(s):

    Huge Table

    idattr

    Filters


    *Performance will vary depending on how much CSS is being applied to the table rows and cells, and whether that CSS was written with performance in mind. Whatever filtering strategy you use, there's not much you can do to make a heavily- or inefficiently-styled table perform well, other than load less of it (as others have suggested).

提交回复
热议问题