155

I've been Googling and searching Stack Overflow for a while, but I just can't get around this problem.

I have a standard HTML table, containing, say, fruit. Like so:

<table>
   <tr>
      <td>Apple</td>
      <td>Green</td>
   </tr>
   <tr>
      <td>Grapes</td>
      <td>Green</td>
   </tr>
   <tr>
      <td>Orange</td>
      <td>Orange</td>
   </tr>
</table>

Above this I have a text box, which I would like to search the table as a user types. So, if they type Gre for example, the Orange row of the table would disapear, leaving the Apple and Grapes. If they carried on and typed Green Gr the Apple row should disapear, leaving just grapes. I hope this is clear.

And, should the user delete some or all of their query from the text box, I should like all of the rows that now match the query to reappear.

While I know how to remove a table row in jQuery, I have little idea about how to go about doing the search and removing rows selectively based on this. Is there a simple solution to this? Or a plugin?

If anyone could point me in the right direction it would be brilliant.

Thank you.

samiles
  • 3,768
  • 12
  • 44
  • 71

11 Answers11

338

I created these examples.

Simple indexOf search

var $rows = $('#table tr');
$('#search').keyup(function() {
    var val = $.trim($(this).val()).replace(/ +/g, ' ').toLowerCase();

    $rows.show().filter(function() {
        var text = $(this).text().replace(/\s+/g, ' ').toLowerCase();
        return !~text.indexOf(val);
    }).hide();
});

Demo: http://jsfiddle.net/7BUmG/2/

Regular expression search

More advanced functionality using regular expressions will allow you to search words in any order in the row. It will work the same if you type apple green or green apple:

var $rows = $('#table tr');
$('#search').keyup(function() {

    var val = '^(?=.*\\b' + $.trim($(this).val()).split(/\s+/).join('\\b)(?=.*\\b') + ').*$',
        reg = RegExp(val, 'i'),
        text;

    $rows.show().filter(function() {
        text = $(this).text().replace(/\s+/g, ' ');
        return !reg.test(text);
    }).hide();
});

Demo: http://jsfiddle.net/dfsq/7BUmG/1133/

Debounce

When you implement table filtering with search over multiple rows and columns it is very important that you consider performance and search speed/optimisation. Simply saying you should not run search function on every single keystroke, it's not necessary. To prevent filtering to run too often you should debounce it. Above code example will become:

$('#search').keyup(debounce(function() {
    var val = $.trim($(this).val()).replace(/ +/g, ' ').toLowerCase();
    // etc...
}, 300));

You can pick any debounce implementation, for example from Lodash _.debounce, or you can use something very simple like I use in next demos (debounce from here): http://jsfiddle.net/7BUmG/6230/ and http://jsfiddle.net/7BUmG/6231/.

dfsq
  • 191,768
  • 25
  • 236
  • 258
  • 4
    I'm pretty green with this stuff, but if I want to incorporate this into my table, do I just need to change the `#table` to the `id` of my table? Would there need to be additional changes to work with `` and `` tags? I've included the script and html from the jsfiddle link, changing the `#id`, but I get no filtering. – JoshP May 31 '13 at 20:22
  • 10
    @JoshP Sctipt works with all the rows. If you want to filter only those inside the `` you should change to `var $rows = $('#id-of-your-table tbody tr');`. – dfsq May 31 '13 at 20:58
  • Thanks for the reply. I had a hunch that was it. Lastly, does this require a particular library be included? – JoshP May 31 '13 at 21:00
  • 2
    @JoshP No, nothing but jQuery is required. Just make sure you run your code in DOMReady or after HTML is loaded. – dfsq May 31 '13 at 21:09
  • @dfsq great answer! I was going row by row and hiding each row but your code is much more straight forward and effective – randomizertech Oct 22 '13 at 18:36
  • what js version should i use – Bongsky Dec 19 '13 at 02:44
  • this works on Jquery 1.5.1+ as far as i know dont have lower versions to check with .. – MasterT Dec 25 '13 at 04:48
  • 2
    I would recommend to enhance this approach because it is quite resource consuming. Put all the refined strings into an array of objects with two fields: a reference to the `` DOMElement and the string. This way, on `keyup()` you search those strings (which is way faster) and have the corresponding rows ready to be manipulated. That first costly setup procedure should then be executed just once right after loading. All these changes are just minor fixes, the actual central part still remains as shown in this answer. This approach is also possible and pretty easy to implement w/o jQuery. – pid Jun 02 '14 at 18:21
  • @dfsq What if we want to skip the first row from check ? – confusedMind Mar 04 '15 at 05:07
  • 3
    @confusedMind Use `$('#table tr:not(:first)')` selector. – dfsq Mar 04 '15 at 06:27
  • 1
    @dfsq can you please explain what the !~ does in the return statement in the filter function? Much appreciated. – Addy75 Jul 06 '15 at 13:57
  • 1
    @Addy75 [Here](http://stackoverflow.com/a/9316656/949476) is a good explanation. Basically, this is shorter check for -1. It should actually be `return text.indexOf(val) == -1;` as much more readable. – dfsq Jul 06 '15 at 14:29
  • This is what I call Poetry, great work and thanks, I used to use a very complicated code, but this is simple and straight forward. **I just wold like to know if I have different tables in the same page with different `ids` and each table has its own filtering `input` field. How can I use this single jQuery to be applied on all of them. Or do I have to create a specific jQuery function to each table?** – SULTAN Jul 23 '15 at 15:43
  • 1
    @SULTAN In this case it's better to create a wrapper function in order to not duplicated code. But the best approach is to wrap the code into custom mini-plugin. Here is an example: http://jsfiddle.net/m0k6pkbb/ – dfsq Jul 23 '15 at 16:21
  • Great, and works, thanks. Is there away to **count the number of filtered rows**? I tried to use `$rows.length` but that keeps giving me the original rows before filtering! – SULTAN Jul 23 '15 at 17:37
  • What if I need to filter rows by just checking the values in the first column? – Fred K Aug 06 '15 at 09:38
  • @FredK The simples thing you can do then is to bind click event to necessary table cells, take text content of the cell, set it as filter input value and trigger "keyup" like if it was entered by typing. Here is an example http://jsfiddle.net/7BUmG/3299/ – dfsq Aug 06 '15 at 09:50
  • @dfsq thanks for answer but I didn't mean "checking" for clicking! I mean: I'd like to get rows by just searching in the values of the first column and not of the other ones. – Fred K Aug 06 '15 at 11:07
  • 1
    @FredK Oh, I misread as "clicking" :) What you need to do is to take text from the first column only `text = $(this.cells[0]).text().replace(/\s+/g, ' ');`. Demo: http://jsfiddle.net/7BUmG/3300/ – dfsq Aug 06 '15 at 11:54
  • @dfsq I get these warnings from my js lint/compiler for the first script: `Unexpected use of '~'.` and `Expected '===' and instead saw '=='.` and this for the 2nd one: `Missing 'new' prefix when invoking a constructor.` – Fred K Aug 06 '15 at 20:31
  • @FredK Regarding `~` I already explained it in [this](http://stackoverflow.com/questions/9127498/how-to-perform-a-real-time-search-and-filter-on-a-html-table/9127872?noredirect=1#comment50492830_9127872) comment. Also use `===` if your jshint requires it. And finally `new RegExp`. – dfsq Aug 06 '15 at 21:08
  • Really a great solution. My Questions: is it possible to search only in a specific column instead all columns? what change will be required? Thanks – Umair Idrees Dec 18 '15 at 11:47
  • @UmairIdrees Check [this](http://stackoverflow.com/questions/9127498/how-to-perform-a-real-time-search-and-filter-on-a-html-table/9127872#comment51629468_9127872) comment. There it searches in the first column `this.cells[0]` where `0` is the index. Change it to necessary one. – dfsq Dec 18 '15 at 13:15
  • How can i make this work if I fill the rows programatically? – Sin5k4 Jan 24 '16 at 12:02
  • @Sin5k4 How exactly do you fill rows? – dfsq Jan 24 '16 at 12:49
  • with jquery: $.each(j.list, function (i, item) { var r = ""; r += "" + item.ID + ""; r += "" + item.firmaName + ""; r += ""; $("#tablecustrows").append(r); – Sin5k4 Jan 24 '16 at 12:55
  • @Sin5k4 In this case nothing changes, just initialise `$rows` after the table in built. – dfsq Jan 24 '16 at 13:04
  • interesting i couldnt get it to work with :/ the html is as :
    ID Cust
    and i call the rows with : var $rows = $('#tablecust tbody tr:not(:first)');
    – Sin5k4 Jan 24 '16 at 13:11
  • @Sin5k4 You can create a demo with the problem. – dfsq Jan 24 '16 at 13:26
  • @dfsq: which solution would you suggest if you had to work with a table in Oracle DB (which contains adresses and has 2,6 Mio rows and 10 columns) and you want to search the table as the user types (the frontend is made with PHP)? The reason I am doing this is to prevent the users to enter wrong addresses manually which causes the poor data quality. A solution that allows the users to SELECT values from the Addresses table would resolve the data quality problem. – Adam May 06 '16 at 08:16
  • 1
    @dfsq What if I have two input text fields and each of them searches rows by different columns. How can I combine them? So, for example, result of search by the first input text field may be used as a data for the second one. – Vitalii Romaniv Jun 07 '16 at 18:58
  • Just to mention if you use this answer wrap the $(document).onReady around it when you add it to your code--I had to do this for django. – Peter Kaminski Jun 09 '16 at 15:12
  • How would you do this for a nested table? Search does not filter the nested table. – Vishal Jul 15 '16 at 12:48
  • @Vishal By adjusting `$('#table tr');` selector. – dfsq Jul 15 '16 at 13:37
  • @dfsq I think you misunderstood me. I am sorry for not explaining the problem in detail. What I mean by nested tables is : When you have row and row-details like scenario. When you search for a text that is in row-details, then row details should be filtered but it should also show the parent row of row details. If its not clear then please ask. Thank you for any help. – Vishal Jul 20 '16 at 10:53
  • @Vishal It's clear of course. You need to adjust $('#table tr'); selector accordingly to target nested table rows. – dfsq Jul 20 '16 at 10:58
  • @dfsq sorry again. I would like to explain you that both rowdetails and its parent row are not the same `tr`. They are like this: `rowrow-details....
    `
    – Vishal Jul 20 '16 at 12:02
  • @Vishal It doesn't matter: you need to adjust selector to target whatever tr you need, you have full control over what is searchable. I think adjacent selector is what you need. – dfsq Jul 20 '16 at 12:40
  • A very helpful answer, thank you. The table I have seems to shift ever-so-slightly to the right when searching, and then moves back again as I delete text. The table has a width of 100% and the columns have a specified width, too. Any ideas what might be shifting it? – Federer Mar 03 '17 at 16:47
  • @Federer It's really difficult to say, can be anything. Try to force fixed width layout by applying `table-layout: fixed;` to table. – dfsq Mar 03 '17 at 19:55
  • @dfsq thanks for the great script. I would like to insert a delete icon to text input. I can do that by changing 'type' to 'search'. Using it i can clear the input form but the rows hidden before remain hidden. I have to press enter, too. Is it possible to show the hidden rows again with out pressing enter? – Robert May 03 '17 at 21:07
  • how to you console log if there are no results to be able to show a no results div. – isethi Jul 28 '17 at 19:55
  • @dfsq Thank you for this, it works well! However, it won't find anything when only "i" is entered as an input using "iowa" as an example. It does work when "io" is entered. Is there a reason for this? – GiarcTNA Mar 16 '18 at 12:19
  • @Craig probably something with your implementation. If you could post a non-working demo, I would take a look. – dfsq Mar 16 '18 at 12:26
  • This is really helpful, thanks! I love the debounce functionality. – JohnGIS Sep 25 '19 at 09:20
  • how do I include this in my html file? I tried putting it between `` after the table code but no joy. Input and table IDs are OK, as far as I can tell, what am I doing wrong? – the.real.gruycho Nov 13 '21 at 11:42
14

Here is the best solution for searching inside HTML table while covering all of the table, (all td, tr in the table), pure javascript and as short as possible:

<input id='myInput' onkeyup='searchTable()' type='text'>

<table id='myTable'>
   <tr>
      <td>Apple</td>
      <td>Green</td>
   </tr>
   <tr>
      <td>Grapes</td>
      <td>Green</td>
   </tr>
   <tr>
      <td>Orange</td>
      <td>Orange</td>
   </tr>
</table>

<script>
function searchTable() {
    var input, filter, found, table, tr, td, i, j;
    input = document.getElementById("myInput");
    filter = input.value.toUpperCase();
    table = document.getElementById("myTable");
    tr = table.getElementsByTagName("tr");
    for (i = 0; i < tr.length; i++) {
        td = tr[i].getElementsByTagName("td");
        for (j = 0; j < td.length; j++) {
            if (td[j].innerHTML.toUpperCase().indexOf(filter) > -1) {
                found = true;
            }
        }
        if (found) {
            tr[i].style.display = "";
            found = false;
        } else {
            tr[i].style.display = "none";
        }
    }
}
</script>
Tarik
  • 4,270
  • 38
  • 35
  • 5
    To protect the table header row from disappearing, add id to the row like: and change the final else statement to: if (tr[i].id != 'tableHeader'){tr[i].style.display = "none";} It is not mentioned in the question but I wanted it to cover that to make it comprehensive. – Tarik Nov 01 '16 at 14:36
  • Instead of comparing the id using !=, I suggest changing the final else to this: } else if (!tr[i].id.match('^tableHeader')) { This allows one to have more than one table, each with their own header. More work is needed to parameterize the searchTable function by passing in the table id. – Tom Ekberg Mar 30 '18 at 16:56
  • @Tarik, How to highlight matching string? like any color or bold or magnified it. – Rj_N Sep 06 '22 at 18:16
10

i have an jquery plugin for this. It uses jquery-ui also. You can see an example here http://jsfiddle.net/tugrulorhan/fd8KB/1/

$("#searchContainer").gridSearch({
            primaryAction: "search",
            scrollDuration: 0,
            searchBarAtBottom: false,
            customScrollHeight: -35,
            visible: {
                before: true,
                next: true,
                filter: true,
                unfilter: true
            },
            textVisible: {
                before: true,
                next: true,
                filter: true,
                unfilter: true
            },
            minCount: 2
        });
Yorgo
  • 2,668
  • 1
  • 16
  • 24
5

Pure Javascript Solution :

Works for ALL columns and Case Insensitive :

function search_table(){
  // Declare variables 
  var input, filter, table, tr, td, i;
  input = document.getElementById("search_field_input");
  filter = input.value.toUpperCase();
  table = document.getElementById("table_id");
  tr = table.getElementsByTagName("tr");

  // Loop through all table rows, and hide those who don't match the search query
  for (i = 0; i < tr.length; i++) {
    td = tr[i].getElementsByTagName("td") ; 
    for(j=0 ; j<td.length ; j++)
    {
      let tdata = td[j] ;
      if (tdata) {
        if (tdata.innerHTML.toUpperCase().indexOf(filter) > -1) {
          tr[i].style.display = "";
          break ; 
        } else {
          tr[i].style.display = "none";
        }
      } 
    }
  }
}
Community
  • 1
  • 1
Natesh bhat
  • 12,274
  • 10
  • 84
  • 125
4

I found dfsq's answer its comments extremely useful. I made some minor modifications applicable to me (and I'm posting it here, in case it is of some use to others).

  1. Using class as hooks, instead of table elements tr
  2. Searching/comparing text within a child class while showing/hiding parent
  3. Making it more efficient by storing the $rows text elements into an array only once (and avoiding $rows.length times computation)

var $rows = $('.wrapper');
var rowsTextArray = [];

var i = 0;
$.each($rows, function () {
    rowsTextArray[i] = ($(this).find('.number').text() + $(this).find('.fruit').text())
        .replace(/\s+/g, '')
        .toLowerCase();
    i++;
});

$('#search').keyup(function() {
  var val = $.trim($(this).val()).replace(/\s+/g, '').toLowerCase();
  $rows.show().filter(function(index) {
    return (rowsTextArray[index].indexOf(val) === -1);
  }).hide();
});
span {
  margin-right: 0.2em;
}
<input type="text" id="search" placeholder="type to search" />

<div class="wrapper"><span class="number">one</span><span class="fruit">apple</span></div>
<div class="wrapper"><span class="number">two</span><span class="fruit">banana</span></div>
<div class="wrapper"><span class="number">three</span><span class="fruit">cherry</span></div>
<div class="wrapper"><span class="number">four</span><span class="fruit">date</span></div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Kaya Toast
  • 5,267
  • 8
  • 35
  • 59
3

Thank you @dfsq for the very helpful code!

I've made some adjustments and maybe some others like them too. I ensured that you can search for multiple words, without having a strict match.

Example rows:

  • Apples and Pears
  • Apples and Bananas
  • Apples and Oranges
  • ...

You could search for 'ap pe' and it would recognise the first row
You could search for 'banana apple' and it would recognise the second row

Demo: http://jsfiddle.net/JeroenSormani/xhpkfwgd/1/

var $rows = $('#table tr');
$('#search').keyup(function() {
  var val = $.trim($(this).val()).replace(/ +/g, ' ').toLowerCase().split(' ');

  $rows.hide().filter(function() {
    var text = $(this).text().replace(/\s+/g, ' ').toLowerCase();
    var matchesSearch = true;
    $(val).each(function(index, value) {
      matchesSearch = (!matchesSearch) ? false : ~text.indexOf(value);
    });
    return matchesSearch;
  }).show();
});
Sormano
  • 336
  • 2
  • 9
  • Solid search -- I had to modify it slightly to prevent my Table's headers & footers from disappearing, by changing: `var $rows = $('#WorldPlayersTable tr');` to -- `var $rows = $('#WorldPlayersTable tbody tr');` – Drefetr Jun 29 '17 at 22:22
2

you can use native javascript like this

<script>
function myFunction() {
  var input, filter, table, tr, td, i;
  input = document.getElementById("myInput");
  filter = input.value.toUpperCase();
  table = document.getElementById("myTable");
  tr = table.getElementsByTagName("tr");
  for (i = 0; i < tr.length; i++) {
    td = tr[i].getElementsByTagName("td")[0];
    if (td) {
      if (td.innerHTML.toUpperCase().indexOf(filter) > -1) {
        tr[i].style.display = "";
      } else {
        tr[i].style.display = "none";
      }
    }       
  }
}
</script>
Omar Maher
  • 45
  • 4
0

Datatable JS plugin is also one good alternate to accomedate search feature for html table

var table = $('#example').DataTable();

// #myInput is a <input type="text"> element
$('#myInput').on( 'keyup', function () {
    table.search( this.value ).draw();
} );

https://datatables.net/examples/basic_init/zero_configuration.html

Aditya
  • 2,385
  • 18
  • 25
0

Pure javascript, and new syntax:

const trs = document.getElementById("myTable").getElementsByTagName("tr");

document.getElementById("search").onkeyup = e => {
    for (const tr of trs)
       tr.style.display = tr.innerText.toLowerCase().includes(e.target.value.toLowerCase()) ? "" : "none";
}
Akif9748
  • 66
  • 6
0

An alternative (and I have found faster) approach is to use Array.from , which allows map, rather than a slow for loop. Uses pure JS.

I've found this is almost instantaneous on big tables (>1K rows). I am not sure if there is a more elegant way to save running the filter twice.

<input id='myInput' onkeyup='searchTable()' type='text'>

<table id='myTable'>
   <tr>
      <td>Apple</td>
      <td>Green</td>
   </tr>
   <tr>
      <td>Grapes</td>
      <td>Green</td>
   </tr>
   <tr>
      <td>Orange</td>
      <td>Orange</td>
   </tr>
</table>

<script>
function searchTable(){
    var term, table;
    // get term to search
    term = document.getElementById("myInput").value.toLowerCase();
    
    // get table rows, handle as array.
    table = Array.from(document.getElementById("myTable").rows);
    
    // filter non-matching rows; these are set to display='none'
    table.filter(function(el){return el.textContent.toLowerCase().indexOf(term) == 
    -1}).map(x=> x.style.display = "none");
    
    // filter matching rows; these are set to display =''
    table.filter(function(el){return el.textContent.toLowerCase().indexOf(term) > -1}).map(x=> x.style.display = "");
}
</script>

I use a backend with templating to create the tables, if so, it's easy enough to keep your header row by adding a class to each tr:

<tr class="rowClass"> ... </tr>

and then modifying the table creation as follows to only iterate over those rows:

table = Array.from(document.getElementsByClassName("rowClass"));
A.Fowler
  • 81
  • 7
-1

If you can separate html and data, you can use external libraries like datatables or the one i created. https://github.com/thehitechpanky/js-bootstrap-tables

This library uses keyup function to reload tabledata and hence it appears to work like search.

function _addTableDataRows(paramObjectTDR) {
    let { filterNode, limitNode, bodyNode, countNode, paramObject } = paramObjectTDR;
    let { dataRows, functionArray } = paramObject;
    _clearNode(bodyNode);
    if (typeof dataRows === `string`) {
        bodyNode.insertAdjacentHTML(`beforeend`, dataRows);
    } else {
        let filterTerm;
        if (filterNode) {
            filterTerm = filterNode.value.toLowerCase();
        }
        let serialNumber = 0;
        let limitNumber = 0;
        let rowNode;
        dataRows.forEach(currentRow => {
            if (!filterNode || _filterData(filterTerm, currentRow)) {
                serialNumber++;
                if (!limitNode || limitNode.value === `all` || limitNode.value >= serialNumber) {
                    limitNumber++;
                    rowNode = _getNode(`tr`);
                    bodyNode.appendChild(rowNode);
                    _addData(rowNode, serialNumber, currentRow, `td`);
                }
            }
        });
        _clearNode(countNode);
        countNode.insertAdjacentText(`beforeend`, `Showing 1 to ${limitNumber} of ${serialNumber} entries`);
    }
    if (functionArray) {
        functionArray.forEach(currentObject => {
            let { className, eventName, functionName } = currentObject;
            _attachFunctionToClassNodes(className, eventName, functionName);
        });
    }
}