I'm writing a simple html page containing multiple table
<table class="tosearch">
<thead>
<tr>
<th>...</th>
</tr>
</thead>
<tbody>
<tr>
<td>...</td>
</tr>
<tr>
<td>...</td>
</tr>
</tbody>
</table>
Above each table I've placed an input text box
<input type="text" id="searchInput" placeholder="Search for data...">
to filter the table's data with the following Js script (credits to chatgpt)
// script.js
document.addEventListener("DOMContentLoaded", function () {
const searchInput = document.getElementById("searchInput");
// Get all tables in the document
const tablesToSearch = document.querySelectorAll(".tosearch");
// Event listener for search input
searchInput.addEventListener("input", function () {
const filter = searchInput.value.toLowerCase();
// Filter all tables
tablesToSearch.forEach(function (table) {
filterTable(table, filter);
});
});
function filterTable(table, filter) {
const rows = table.getElementsByTagName("tr");
for (let i = 1; i < rows.length; i++) {
const row = rows[i];
const cells = row.getElementsByTagName("td");
let shouldShow = false;
for (let j = 0; j < cells.length; j++) {
const cell = cells[j];
const text = cell.textContent.toLowerCase();
if (text.includes(filter)) {
shouldShow = true;
break;
}
}
row.style.display = shouldShow ? "" : "none";
}
}
});
Everything works nice, but if I would like to have multiple tables on the same page, each with a text input above it.
If I just copy and paste the html code for the text input and the table, when I try to search the script filter on all tables, while I would like to filter only on the associated table, something like
textInput for table1 only
table1
textInput for table2 only
table2
.
.
.
I hope I explained myself good enough, otherwise I'll make some edits.
The easiest way was to make it a reusable function:
function filterTable(table, filter) {
const rows = table.getElementsByTagName("tr");
for (let i = 1; i < rows.length; i++) {
const row = rows[i];
const cells = row.getElementsByTagName("td");
let shouldShow = false;
for (let j = 0; j < cells.length; j++) {
const cell = cells[j];
const text = cell.textContent.toLowerCase();
if (text.includes(filter)) {
shouldShow = true;
break;
}
}
row.style.display = shouldShow ? "" : "none";
}
}
/* This method is called onload now, but is limited to arguments you have passed in.*/
function linkInputToTablesSearch( searchInput, tablesToSearch ){
searchInput.addEventListener("input", function () {
const filter = searchInput.value.toLowerCase();
tablesToSearch.forEach(function (table) {
filterTable(table, filter);
});
});
};
document.addEventListener("DOMContentLoaded", function () {
document.querySelectorAll( 'input' ).forEach(input => {
linkInputToTablesSearch( input, [ input.nextElementSibling ] );
});
});
<input type="text" placeholder="Search for data...">
<table>
<tbody>
<tr>
<td>a1</td>
<td>a2</td>
<td>a3</td>
<td>a4</td>
<td>a5</td>
</tr>
<tr>
<td>b1</td>
<td>b2</td>
<td>b3</td>
<td>b4</td>
<td>b5</td>
</tr>
</tbody>
</table>
You can now link the table and the input by calling linkInputToTablesSearch
with the input and an array of tables as the arguments.
But your filtering has a couple of issues, the break
is stopping it and you will never be able to hide rows after a match. I have a more simplified version of the filter here:
function filterTable( table, query ){
const regex = query && new RegExp( query, 'gi' );
[...table.querySelectorAll( 'tr' )].forEach(row => {
// Since you are filtering the rows, maybe skip the step of checking every cell individually. Although this would allow it to bleed (so if cell 1 contains a, and cell 2 contains b, the row contains the string 'ab'). If you want to avoid that, just filter through the cells here with the same regex.
row.hidden = !!query && !regex.test( row.textContent );
});
}
function linkInputToTables( input, tables ){
// Ensure you have an array of tables. With this, you could also just pass in a single table selected with getElementById or querySelector
tables = tables instanceof HTMLTableElement ? [ tables ] : tables;
input.addEventListener( 'input', event => {
tables.forEach(table => filterTable( table, input.value ));
});
}
document.querySelectorAll( 'label' ).forEach(label => {
const input = label.querySelector( 'input' );
const target = document.querySelector( label.dataset.target );
if( input && target ) linkInputToTables( input, target );
})
#table1 { border: 3px solid red }
#table2 { border: 3px solid blue }
<table id="table1">
<tr>
<td>a1</td>
<td>a2</td>
<td>a3</td>
<td>a4</td>
</tr>
<tr>
<td>b1</td>
<td>b2</td>
<td>b3</td>
<td>b4</td>
</tr>
</table>
<table id="table2">
<tr>
<td>a1</td>
<td>a2</td>
<td>a3</td>
<td>a4</td>
</tr>
<tr>
<td>b1</td>
<td>b2</td>
<td>b3</td>
<td>b4</td>
</tr>
</table>
<label data-target="#table1">
<span>Just table 1</span>
<input type="search" value="" id= />
</label>
<label data-target="#table2">
<span>Just table 2</span>
<input type="search" value="" id= />
</label>