I've tried using the below js code to filter table rows. The code is originally from w3schools but I've done some modification to target all my input values. The filtering works great for one column but as fast as I try to input a value on a second column, it overwrites the previous filter.
For exampe if I filter for "Test" in column 1 it works great and hides the second row. If I after that also filter column 2 for id "2" it will hide the first row and instead display the second row for id 2. Is it possible to modify the code so that it only filters on the rows that are left and shown, not all the rows all together. Ive tried several hours trying to target only tr[i].style.display != "none"; but no success. My goal is something like this: DataTables. I've seen and read tons of other threads on here regarding this issue but nothing seems to work. Appreciate all the help and guidance.
Name
ID
Test
1
Another test
2
[Input field for filter]
[Input field for filter]
$(document).ready(function() {
document.querySelectorAll('.search').forEach(item => {
item.addEventListener('keyup', event => {
var input, filter, table, tr, td, i, txtValue;
input = event.target;
filter = input.value.toUpperCase();
table = document.getElementById("example");
tr = table.getElementsByTagName("tr");
for (i = 0; i < tr.length; i++) {
td = tr[i].getElementsByTagName("td")[event.target.getAttribute('data-value')];
if (td) {
txtValue = td.textContent || td.innerText;
if (txtValue.toUpperCase().indexOf(filter) > -1) {
tr[i].style.display = "";
} else {
tr[i].style.display = "none";
}
}
}
})
})
} );
Your code was a mix of vanillaJS and jQuery, and tagged for both, so I went with jquery since it's a little more concise and easier to read.
Basically, you need to check both cells in each row against both search filters every time the key is pressed. To do so, I made things a tiny bit easier by giving the data TR rows a class so we're not searching the header or the cells with the search boxes. Then, I just compare filter 1 with cell 1 and filter 2 with cell 2 for each row. If there is a match for either, that row stays visible, otherwise it's hidden. I had to add in some logic in case there wasn't a value in either filter input.
Couple other notes:
$('.search').eq(0) is the same as the FIRST element with the class search
$(el).find('td').eq(0).text().trim().toLowerCase() is the same as find the FIRST TD tag's text, trim off the extra whitespace and convert to lowercase
$(document).ready(function() {
$('.search').keyup(function() {
let search1 = $('.search').eq(0).val().toLowerCase();
let search2 = $('.search').eq(1).val().toLowerCase();
$('.s-table tr.data').each(function(i, el) {
let val1 = $(el).find('td').eq(0).text().trim().toLowerCase()
let val2 = $(el).find('td').eq(1).text().trim().toLowerCase()
let ok = (search1 && val1.indexOf(search1) !== -1) || (search2 && val2.indexOf(search2) !== -1)
if (ok) $(el).closest('tr').show();
else $(el).closest('tr').hide();
})
return
});
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<table class="s-table">
<thead>
<tr>
<th>Name</th>
<th>ID</th>
</tr>
</thead>
<tbody>
<tr class='data'>
<td>Test</td>
<td>1</td>
</tr>
<tr class='data'>
<td>Another test</td>
<td>2</td>
</tr>
<tr>
<td><input data-col='0' class='search' placeholder='filter'></td>
<td><input data-col='1' class='search' placeholder='filter'></td>
</tr>
</tbody>
</table>
Related
first I'm sorry if at some point I express myself badly, English is not my native language. I am developing an application in which the user sends 2 values through a form and in another page I use one of those data (string with comma separated options) to show a specific table and hide the others, and with the second data (Integer) I show one of the rows of that table.
What I already have:
I have the form and send the data through the Query String, I capture that data, I assign a variable to the integer and to the text string I separate it by commas and create an array.
URL Example: app.tables.example/?id=123&ops=option1%2c+option2
//Read parameters sent by form
const param = new Proxy(new URLSearchParams(window.location.search), {
get: (searchParams, prop) => searchParams.get(prop)
});
//Assign integer to a variable
let num = param.id;
//Assign options to a variable
let op = param.ops;
//Separate string with commas
let opsplit = op.split(',');
Up to here everything is perfect, I can print all the variables without any problem, now I need to compare the array with the id of each table, show the one that corresponds and hide the others. (The id of the tables is the same that user passes with the text string).
The tables look something like this:
<div id="option1" class="table-1">
<table width="100%">
<thead>
<tr>
<th align="left">Option1</th>
<th align="left">Integer</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">Number</td>
<td align="left">Info</td>
</tr>
<tr>
<td align="left">1</td>
<td align="left">textblock</td>
</tr>
<tr>
<td align="left">2</td>
<td align="left">textblock</td>
</tr>
</tbody>
</table>
</div>
//As you can see the id of each table corresponds to what the user chose
<div id="option2" class="table-1">
<table width="100%">
<thead>
<tr>
<th align="left">Option2</th>
<th align="left">Integer</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">Number</td>
<td align="left">Info</td>
</tr>
<tr>
<td align="left">1</td>
<td align="left">textblock</td>
</tr>
<tr>
<td align="left">2</td>
<td align="left">textblock</td>
</tr>
</tbody>
</table>
</div>
The problem:
I'm supposed to use the array elements in a "for" loop and compare them with the id of each table, then show that table and hide others, but I don't know exactly how to do it.
function myFunction() {
var input, filter, table, tr, td, i, y, txtValue;
for (r = 0; r<opsplit.length; r++){
input = opsplit[r];
filter = function(x){
return x.toUpperCase();
};
opsplit = opsplit.map(filter);
}
//When I test this, it does not return any value,
//innerHTML error
for(y = 0; y<opsplit.length; y++){
table = document.getElementById(opsplit[y]).innerHTML;
//I use this section to test, it should show me the row,
//but since the previous loop failed, it does nothing.
// What I really need is to show the whole
// table where this data is located and hide the other tables.
tr = table.getElementsByTagName("tr");
for (i = 0; i < tr.length; i++) {
td = tr[i].getElementsByTagName("td")[0];
if (td) {
txtValue = td.textContent || td.innerText;
if (txtValue.toUpperCase().indexOf(filter) > -1) {
tr[i].style.display = "";
} else {
tr[i].style.display = "none";
}
}
}
}
}
myFunction();
I am really stuck at the moment and would appreciate any help. What is the correct way to use the string array, and how can I hide the tables that the user does not need?
Ok, thanks to those who took the time to write a comment, but I found the solution, I had to convert the form data to a string and an integer. Then I was able to compare that string to the classes id. I am writing the answer in case anyone finds it useful.
//Read parameters sent by form
const param = new Proxy(new URLSearchParams(window.location.search), {
get: (searchParams, prop) => searchParams.get(prop)
});
//Assign number to a variable
let num = param.id;
//Convert to integer
let numint = parseInt(num);
//Assign options to a variable
let op = param.ops;
//Convert to String
let opstr = op.string();
//Separate string with commas
let opsplitstr = opstr.split(',');
//Assign class to a variable
tableclass = document.getElementsByClassName('table-1');
//Compare table class ids with string array
if (opsplitstr[0] == tableclass[0].id{
}
//We need a loop if we need compare all elements
TLDR: The URL does not send information about the data type, so I had to read it and convert it according to my need.
I want to search the HTML table with its min and max column values and display the searched row in the table. I have made a code for this and it is working fine to some extent. But even when I am searching 2, 4, 7 in the table search, it is still searching for 10-20, 30-40 and 60-70 respectively.
Please take a look and suggest me what should I change so that it should work perfect.
function myFunction() {
var input, filter, table, tr, td, i, txtValue;
input = document.getElementById("myInput").value;
table = document.getElementById("myTable");
tr = table.getElementsByTagName("tr");
for (i = 0; i < tr.length; i++) {
td = tr[i].getElementsByTagName("td")[0];
td2 = tr[i].getElementsByTagName("td")[1];
if (td) {
txtValue = td.textContent || td.innerText;
txtValue2 = td2.textContent || td2.innerText;
if (input >= txtValue && input <= txtValue2) {
tr[i].style.display = "";
} else {
tr[i].style.display = "none";
}
}
}
}
<input type="text" id="myInput" onkeyup="myFunction()" placeholder="Search by min-max values" title="Type in a name">
<table id="myTable">
<tr class="header">
<th style="width:60%;">Min</th>
<th style="width:40%;">Max</th>
<th style="width:40%;">Output</th>
</tr>
<tr>
<td>10</td>
<td>20</td>
<td>Germany</td>
</tr>
<tr>
<td>30</td>
<td>40</td>
<td>Sweden</td>
</tr>
<tr>
<td>60</td>
<td>70</td>
<td>UK</td>
</tr>
</table>
All you need is parseInt() when you're comparing data.
Normally when you get values using JS tags, they returns String, so it's pretty obvious you need to covert those to Integer before start using them for comparisons.
Here's a working jsfiddle for your answer - https://jsfiddle.net/exnak6vj/1/
Alternatively, just one more if condition and all the data in the table will be displayed if there's no text in search text area. I've implemented that too in the above jsfiddle.
Off-topic - this is good as long as you're doing it for your own learnings, but to implement something like this in actual project, there are whole lot of JS plugins available in market that too for free like Datatables... etc. Use those, they provides you great flexibility.
In your code the variables txtValue, txtValue2 and input are all strings. Use Number() function to make sure input and txtValue variables are Numbers.
a = "20" //this is how number as string is input
aNum = Number(a)
OR
aNum = parseInt(a, 10)
then you can properly do min and max comparison operations and you will not get 20 when you input 2.
I hope you're having a nice day.
I'm a newbie with JavaScript, specially with WebComponents and HTML templates, I'm developing a simple table that pulls data from a public API Rest, this is the table:
Table
and this is HTML code that's structuring table with the template #row:
<body>
<div id=botones></div>
<table>
<thead>
<tr>
<th>Name</th><th>Amount</th>
</tr>
</thead>
<tbody>
<template id="row">
<tr><td></td><td></td></tr>
</template>
</tbody>
</table>
Next, the code where I do the calling from the API Rest, it's worth mentioning that in the for loop I'm cloning template's content and introducing the data of the API array result and appending to tbody tag every row as the iteration marks.
function generateTable(){
const row = document.querySelector('#row');
const tbody = document.querySelector('table>tbody');
fetch('http://dummy.restapiexample.com/api/v1/employees')
.then(response => {
if(response.status === 200){
return response.json();
}else{
alert("No hay datos");
}
})
.then(data=>{
for(var j=0 ; j<data["data"].length;j++){
const clone = row.content.cloneNode(true);
const tds = clone.querySelectorAll('td');
tds[0].textContent = data["data"][j]["employee_name"];
tds[1].textContent = data["data"][j]["employee_salary"];
tbody.appendChild(clone);
}
})
}
function search(){
var input, filter,table, tr, td, i, txtValue,tbody,temp;
input = inputIndex.value.toUpperCase();
filter = input;
table = document.body.getElementsByTagName('template');
tr = table.content;
// tbody = table.getElementById('#cuerpo_tabla');
// temp = tbody.getElementById('#row');
// tr = temp.getElementsByTagName('tr');
for(i=0;i<tr.length;i++){
td = tr[i].getElementsByClassName("celda")[0];
if(td){
txtValue = td.textContent || td.innerText;
if(txtValue.toUpperCase().indexOf(filter)>-1){
tr[i].style.display ="";
}else{
tr[i].style.display = "none";
}
}
}
}
The problem comes when I try to do an index search, introducing a key and clicking the button of search index, debugger throws that browser is recognizing template #row but is not recognizing its content, row and cells thus for loop can't do iteration and compare the content in rows and next, throwing the search, I tried to do this by DOM as you can see but it does not works.
If someone that knows about this can help me, I'll be grateful!!
Thanks a lot and best regards! :)
I have a table rendered dynamically. There's one <tr class="dolly"> somewhere inside its <tbody> that serves as a reference row - it gets cloned and filled with data later. I need to delete all rows except that one.
What I tried:
for loop: uses an increment which quickly gets invalid as the rows are deleted
while loop: continues until all rows are deleted, which never happens because of the condition
Please let me know if you have any ideas. Please no jQuery.
use document.querySelectorAll('tr:not(.dolly)') to select all tr's except with class .dolly and then iterate over it to remove the filtered tr's.
document.querySelectorAll('table tr:not(.dolly)').forEach((tr) => {
tr.remove();
});
<table>
<tr class="dolly">
<td>One</td>
</tr>
<tr>
<td>Two</td>
</tr>
<tr>
<td>Three</td>
</tr>
</table>
I am gonna share my solution here.
function deleteAllOtherRowExceptOne(exceptionIndex) {
const tableBody = document.querySelector('#my-table-tbody')
const tableBodyRowLength = tableBody.rows.length
let deleteIndex = 0
for (let i = 0; i < tableBodyRowLength; i++) {
if(i == exceptionIndex){
deleteIndex++
} else {
tableBody.deleteRow(deleteIndex)
}
}
}
Here is my solution for this question.
// Fetch all rows
const rows = document.querySelectorAll('tr');
// Iterate through the rows and remove those that do not have the desired
class
const className = 'class-name';
rows.forEach(row => {
if (!row.classList.contains(className)) {
row.remove();
}
});
I took refernce from here - https://bbbootstrap.com/code/delete-all-table-rows-except-one-given-class-javascript-61232938
I have an html table in my view that I want to filter with multiple filters. In this case, I have 3 filters, but I can have much more.
Here is a little part of the code, to show the problem
$(document).ready(function () {
$('#datefilterfrom').on("change", filterRows);
$('#datefilterto').on("change", filterRows);
$('#projectfilter').on("change", filterProject);
$('#servicefilter').on("change", filterService);
});
function filterRows() {
var from = $('#datefilterfrom').val();
var to = $('#datefilterto').val();
if (!from && !to) { // no value for from and to
return;
}
from = from || '1970-01-01'; // default from to a old date if it is not set
to = to || '2999-12-31';
var dateFrom = moment(from);
var dateTo = moment(to);
$('#testTable tr').each(function (i, tr) {
var val = $(tr).find("td:nth-child(2)").text();
var dateVal = moment(val, "DD/MM/YYYY");
var visible = (dateVal.isBetween(dateFrom, dateTo, null, [])) ? "" : "none"; // [] for inclusive
$(tr).css('display', visible);
});
}
function filterProject() {
let dumb = this.options.selectedIndex;
dumb = this.options[dumb].innerHTML;
var filter, table, tr, td, i;
filter = dumb.toUpperCase();
table = document.getElementById("testTable");
tr = table.getElementsByTagName("tr");
for (i = 0; i < tr.length; i++) {
td = tr[i].getElementsByTagName("td")[2];
if (td) {
if (td.innerHTML.toUpperCase().indexOf(filter) > -1) {
tr[i].style.display = "table-row";
} else {
tr[i].style.display = "none";
}
}
}
}
function filterService() {
let dumb = this.options.selectedIndex;
dumb = this.options[dumb].innerHTML;
var filter, table, tr, td, i;
filter = dumb.toUpperCase();
table = document.getElementById("testTable");
tr = table.getElementsByTagName("tr");
for (i = 0; i < tr.length; i++) {
td = tr[i].getElementsByTagName("td")[3];
if (td) {
if (td.innerHTML.toUpperCase().indexOf(filter) > -1) {
tr[i].style.display = "table-row";
} else {
tr[i].style.display = "none";
}
}
}
}
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://rawgit.com/moment/moment/2.2.1/min/moment.min.js"></script>
<div class="row">
<div class="col-md-3">
<h4>Date from</h4>
<input type="date" class="form-control" id="datefilterfrom" data-date-split-input="true">
</div>
<div class="col-md-3">
<h4>Date to</h4>
<input type="date" class="form-control" id="datefilterto" data-date-split-input="true">
</div>
<div class="col-md-2">
<h4>Project</h4>
<select id="projectfilter" name="projectfilter" class="form-control"><option value="1">Test project</option><option value="2">Test2</option></select>
</div>
<div class="col-md-2">
<h4>Service</h4>
<select id="servicefilter" name="servicefilter" class="form-control"><option value="1">Test service</option><option value="2">Test2 service</option></select>
</div>
</div>
<table id="testTable" class="table">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Date</th>
<th scope="col">Project</th>
<th scope="col">Service</th>
</tr>
</thead>
<tbody id="report">
<tr>
<td class="proposalId">9</td><td> 17/07/2018</td> <td> Test project</td><td> Test service</td>
</tr>
<tr><td class="proposalId">8</td><td> 18/07/2018</td><td> Test project</td><td> Test2 service</td></tr>
<tr><td class="proposalId">7</td><td> 17/07/2018</td><td> Test2</td><td> Test2 service</td></tr>
<tr style=""><td class="proposalId">3</td><td> 19/07/2018</td><td> Test2</td><td> Test service</td></tr>
</tbody>
</table>
If you set filters like this
You will have this
This is not right. Because I need to have only test 2 project, so one row.
Where is my problem and How I can solve it?
Here is codepen for code
https://codepen.io/suhomlineugene/pen/pZqyEN
Right now you have a separate function for each of your filters, each of which ignores the settings from the other filters and overwrites their results.
Instead you'll need to combine those into a single function which takes all the filters into account.
Rather than literally combining all the code into one complex function, which would be difficult to maintain, one approach would be to have a single master function that makes all the rows visible, then calls each of the other filter functions in turn; those functions would only hide the rows they're filtering out. What's left visible at the end would be the rows that match all the filter selections.
$(document).ready(function () {
$('#datefilterfrom, #datefilterto, #projectfilter, #servicefilter').on("change", filterAll);
});
function filterAll() {
$('#testTable tr').show();
filterRows();
filterProject();
filterService();
// ...etc
}
function filterRows() { // repeat for filterProject(), filterService(), etc
// same as your original code, except only hide non-matching rows, do not
// show matching rows (because filterAll() already took care of that, and
// you don't want to undo what other filters may have hidden.)
}
(Alternatively, instead of showing everything and then having each individual filter hide rows incrementally, you could have filterAll() build an array of all the rows, pass it to the individual filter functions which will remove items from it, then use the end result to show/hide the appropriate rows in one go.)
Not going to rewrite this all for you but will give you a basic outline for the text only searches:
Create array of the filter data from the top inputs. By adding a data-col to each of those filter controls you can easily determine which column in table to match to
So the filters array would look something like:
[
{col:3, value:'test project'}
]
Then use jQuery filter() on the rows and use Array#every() on the filterValues array and look for the matching cell text using the column index from each filter object
var $rows = $('tbody#report tr')
// add a class `table-filter` to all the top filtering elements
var $filters = $('.table-filter').change(function() {
// create array of filter objects
var filterArr = $filters.filter(function() {
return this.value
}).map(function() {
var $el = $(this);
var value = $el.is('select') ? $el.find(':selected').text() : $el.val()
return {
col: $el.data('col'),
value: value.toLowerCase()
}
}).get();
if (!filterArr.length) {
// no filters show all rows
$rows.show()
} else {
// hide all then filter out the matching rows
$rows.hide().filter(function() {
var $row = $(this);
// match every filter to whole row
return filterArr.every(function(filterObj, i) {
var cellText = $row.find('td').eq(filterObj.col).text().trim().toLowerCase();
return cellText.includes(filterObj.value);
})
})
// show the matches
.show()
}
});
Working demo for the two text search fields