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
Related
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>
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 am trying to sort HTML table using addEventListener in my JavaScript, by pushing any table column header. It basically works, but only either ascending or descending order.
It should work as a toggle; when you push the same column header again, the sort order should change as descending.
I am quite a beginner so I do not know how to make element.addEventListener function to activate my sortTable and reverseTable functions in turns while pushing the same column header.
I guess the problem is in this block, as the addEventListener takes in account only either sortTable function or reverseTable function, depending on which one is the latter one in the list. In this case, the reverseTable function, so it sorts in descending order and only once:
table.querySelectorAll('th') // get all the table header elements
.forEach((element, columnNo)=>{ // add a click handler for each
element.addEventListener('click', event => {
sortTable(table, columnNo); //call a function which sorts the table by a given column number
reverseTable(table,columnNo); //call a function to sort in reverse order
})
})
The whole code with table data is here:
const table = document.querySelector('table'); //get the table to be sorted
table.querySelectorAll('th') // get all the table header elements
.forEach((element, columnNo)=>{ // add a click handler for each
element.addEventListener('click', event => {
sortTable(table, columnNo); //call a function which sorts the table by a given column number
reverseTable(table,columnNo); //call a function to sort in reverse order
})
})
function sortTable(table, sortColumn){
// get the data from the table cells
const tableBody = table.querySelector('tbody')
const tableData = table2data(tableBody);
// sort the extracted data
tableData.sort((a, b)=>{
if(a[sortColumn] > b[sortColumn]){
return 1;
}
return -1;
})
//put the sorted data back into the table
data2table(tableBody, tableData);
}
function reverseTable(table, sortColumn){
// get the data from the table cells
const tableBody = table.querySelector('tbody')
const tableData = table2data(tableBody);
// sort the extracted data
tableData.reverse((a, b)=>{
if(a[sortColumn] > b[sortColumn]){
return -1;
}
return 1;
})
// put the sorted data back into the table
data2table(tableBody, tableData);
}
// this function gets data from the rows and cells
// within an html tbody element
function table2data(tableBody){
const tableData = []; // create the array that'll hold the data rows
tableBody.querySelectorAll('tr')
.forEach(row=>{ // for each table row...
const rowData = []; // make an array for that row
row.querySelectorAll('td') // for each cell in that row
.forEach(cell=>{
rowData.push(cell.innerText); // add it to the row data
})
tableData.push(rowData); // add the full row to the table data
});
return tableData;
}
// this function puts data into an html tbody element
function data2table(tableBody, tableData){
tableBody.querySelectorAll('tr') // for each table row...
.forEach((row, i)=>{
const rowData = tableData[i]; // get the array for the row data
row.querySelectorAll('td') // for each table cell ...
.forEach((cell, j)=>{
cell.innerText = rowData[j]; // put the appropriate array element into the cell
})
tableData.push(rowData);
});
}
<table>
<thead>
<tr><th>Name</th><th>Surname</th><th>Age</th</tr>
</thead>
<tbody>
<tr><td>John</td><td>Smith</td><td>62</td></tr>
<tr><td>Dylan</td><td>Jones</td><td>37</td></tr>
<tr><td>Alan</td><td>Shearer</td><td>55</td></tr>
<tr><td>Ringo</td><td>Starr</td><td>52</td></tr>
</tbody>
</table>
So I solved your problem by adding attributes to <th> tags, now they save their state and on next click, we know what to do, also I change arrow function(name() => {}) to regular function(function name(){}) to have access to this for more details of their difference please read this article, so here's your code, I hope I helped you and it works as expected
*EDIT: as #user4642212 mentioned in comments data attributes are much more convenient way to solve this problem so I edited my actual code
const table = document.querySelector('table'); //get the table to be sorted
table.querySelectorAll('th') // get all the table header elements
.forEach((element, columnNo) => { // add a click handler for each
element.addEventListener('click', function(event) {
const isReverse = (this.dataset.reverse == 'true')
this.dataset.reverse = !isReverse
console.log(isReverse)
if (isReverse) sortTable(table, columnNo); //call a function which sorts the table by a given column number
else reverseTable(table, columnNo); //call a function to sort in reverse order
})
})
function sortTable(table, sortColumn) {
// get the data from the table cells
const tableBody = table.querySelector('tbody')
const tableData = table2data(tableBody);
// sort the extracted data
tableData.sort((a, b) => {
if (a[sortColumn] > b[sortColumn]) {
return 1;
}
return -1;
})
//put the sorted data back into the table
data2table(tableBody, tableData);
}
function reverseTable(table, sortColumn) {
// get the data from the table cells
const tableBody = table.querySelector('tbody')
const tableData = table2data(tableBody);
// sort the extracted data
tableData.reverse((a, b) => {
if (a[sortColumn] > b[sortColumn]) {
return -1;
}
return 1;
})
// put the sorted data back into the table
data2table(tableBody, tableData);
}
// this function gets data from the rows and cells
// within an html tbody element
function table2data(tableBody) {
const tableData = []; // create the array that'll hold the data rows
tableBody.querySelectorAll('tr')
.forEach(row => { // for each table row...
const rowData = []; // make an array for that row
row.querySelectorAll('td') // for each cell in that row
.forEach(cell => {
rowData.push(cell.innerText); // add it to the row data
})
tableData.push(rowData); // add the full row to the table data
});
return tableData;
}
// this function puts data into an html tbody element
function data2table(tableBody, tableData) {
tableBody.querySelectorAll('tr') // for each table row...
.forEach((row, i) => {
const rowData = tableData[i]; // get the array for the row data
row.querySelectorAll('td') // for each table cell ...
.forEach((cell, j) => {
cell.innerText = rowData[j]; // put the appropriate array element into the cell
})
tableData.push(rowData);
});
}
<table>
<thead>
<tr>
<th data-reverse="true">Name</th>
<th data-reverse="true">Surname</th>
<th data-reverse="true">Age</th</tr>
</thead>
<tbody>
<tr>
<td>John</td>
<td>Smith</td>
<td>62</td>
</tr>
<tr>
<td>Dylan</td>
<td>Jones</td>
<td>37</td>
</tr>
<tr>
<td>Alan</td>
<td>Shearer</td>
<td>55</td>
</tr>
<tr>
<td>Ringo</td>
<td>Starr</td>
<td>52</td>
</tr>
</tbody>
</table>
Hey i have this code:
<body>
<table>
<tr>
<td class="first">100</td>
</tr>
</table>
<h4 class=curs style="display:none">10</h4>
<script>
document.body.onload = function(){
var firstTdVal = document.getElementsByClassName('first')[0].innerHTML;
var secondTdVal = document.getElementsByClassName('curs')[0].innerHTML;
var valueToBeShown = parseInt(firstTdVal)/ parseInt(secondTdVal);
document.getElementsByClassName('first')[0].innerHTML = valueToBeShown ;
}
</script>
</body>
As you see ".first" has a number in it,this number is divied to ".curs" and the result is showed in ".first" too.Now the problem is that for exemple i add 100 more td's with class ".second,.third...,.hundred" in table.How to make script to do the same for all td's as it does for the ".first"(devide to ".curs").How do i do this in my JS by keeping it complex.
Use document.querySelectorAll to get an array of matched elements (matched with CSS selector), then loop through them using forEach, applying you logic one td at a time. Like this:
// querySelector gets the first element matched. textContent get the text of that element
var cursValue = parseInt(document.querySelector(".curs").textContent);
// querySelectorAll get an array of all the matched elements
var tds = document.querySelectorAll("td");
// loop through that array one td at a time
tds.forEach(function(td){
// get the text of the current td
var value = parseInt(td.textContent);
// if the value is not valid (a string for example) return and don't process anymore for this td (go straight to the next one).
if(isNaN(value)) return;
// calculate the new value
value = value / cursValue;
// change the text of this td (update it to the new value)
td.textContent = value;
});
NOTE: querySelector and querySelectorAll match elements using CSS selectors, so to match an element using a class the selector should be ".className", to match it using an ID: "#someID", ... All CSS selectors are accepted (even this one: "#anID>li.some-class a:not([href])").
NOTE2: tds is an array, so if you don't want to use forEach you can use a normal for loop (for(var i = 0; i < tds.length; i++) ...).
This will iterate over your table (be sure to set the table ID) (open dev console to view output but it's pretty straight forward.)
var table = document.getElementById("myTable");
for (var row of table.rows) {
for (var col of row.cells) {
console.log(col.className, col.innerHTML); //Class names and the values of the elements.
}
}
If you need anymore help please ask because I do not fully understand what you're trying to do here.
Here's a way where you put the number to be divided by in the first td, the number to divide by in the second td, and the result will be placed in the third td.
var trs = document.getElementsByTagName('tr');
for (var i = 0; i< trs.length; i++) {
var tds = trs[i].getElementsByTagName('td'),
first = tds[0].textContent,
second = tds[1].textContent,
third = tds[2],
result = (parseInt(first) / parseInt(second));
third.innerHTML = result;
}
<table>
<tr>
<td>10</td>
<td>2</td>
<td></td>
</tr>
<tr>
<td>4</td>
<td>2</td>
<td></td>
</tr>
</table>
I am using a JavaScript snippet to show a responsive table, setting the headers on mobile via attributes. This works, but, if I use a second table with the same class, it goes all wrong on mobile (please resize your screen to see this); the headers of.
What am I doing wrong here and how can I fix this?
This is the HTML:
<table class="test">
<thead>
<tr>
<th>Bla</th>
<th>Bla</th>
<th>Bla</th>
</tr>
</thead>
<tbody>
<tr>
<td>Bla</td>
<td>Blabla</td>
<td>Blablabla</td>
</tr>
</tbody>
</table>
<table class="test">
<thead>
<tr>
<th>Not</th>
<th>Not</th>
</tr>
</thead>
<tbody>
<tr>
<td>Twatwa</td>
<td>Twatwa</td>
</tr>
</tbody>
</table>
http://codepen.io/anon/pen/QbJqVv
Edit: after the new answer, it does show table headers on the second table now, but not the correct ones. It just puts the table headers of the first table, into the second.
As I wrote in the comments, you need to handle each table separately. For .querySelectorAll('.test th') will simply give you all th elements, irregardless of which table they belong to.
Here's a quick example of how this could be done.
// for each .test
[].forEach.call(document.querySelectorAll('.test'), function (table) {
// get header contents
var headers = [].map.call(table.querySelectorAll('th'), function (header) {
return header.textContent.replace(/\r?\n|\r/, '');
});
// for each row in tbody
[].forEach.call(table.querySelectorAll('tbody tr'), function (row) {
// for each cell
[].forEach.call(row.cells, function (cell, headerIndex) {
// apply the attribute
cell.setAttribute('data-th', headers[headerIndex]);
});
});
});
demo: http://codepen.io/anon/pen/NqEXqe
First of all, your HTML is invalid, as you are not closing any of your elements (<tr><td></td></tr> etc) - but that's another issue. Please practice good HTML standards.
You are not using querySelectorAll when selecting your table bodies, so you're only setting the attribute in the first one found.
This revised snippet should achieve what you are trying to do.
var headertext = [],
headers = document.querySelectorAll(".test th"),
tablerows = document.querySelectorAll(".test th"),
tablebody = document.querySelectorAll(".test tbody");
for(var i = 0; i < headers.length; i++) {
var current = headers[i];
headertext.push(current.textContent.replace(/\r?\n|\r/,""));
}
for (var tb = 0; tb < tablebody.length; tb++) {
for (var i = 0, row; row = tablebody[tb].rows[i]; i++) {
for (var j = 0, col; col = row.cells[j]; j++) {
col.setAttribute("data-th", headertext[j]);
}
}
}