I have a javascript code that works well when sorting html tables like this:
<table id="tableBeingDecomm" align="center" render-area="beingDecom">
<thead>
<tr>
<th id="beingDecom"><a href="#">
Server Name
<img src='/icons/sort.png' width='20' height='20' style='display:inline;'>
</a></th>
<th id="beingDecomDate"><a href="#">
Deleting Date
<img src='/icons/sort.png' width='20' height='20' style='display:inline;'>
</a></th>
</tr>
</thead>
<tbody render-action="loop">
<tr render-data="jsonBeingDecom">
<td>{!serverName!}</td>
<td>{!deletingDate!}</td>
</tr>
</tbody>
</table>
but not with this new table below, no errors and the page becomes unresponsive:
<table id="PortsTable" align="center" render-area="dcPorts">
<thead>
<tr>
<th id="ServerNameHeader1"><a href="#">
Server Name
<img src='/icons/sort.png' width='20' height='20' style='display:inline;'>
</a></th>
<th id="ServerNameHeader2"><a href="#">
Destination
<img src='/icons/sort.png' width='20' height='20' style='display:inline;'>
</a></th>
<th>Port 53</th>
<th>Port 88</th>
<th>Port 135</th>
<th>Port 139</th>
<th>Port 389</th>
<th>Port 445</th>
<th>Port 464</th>
<th>Port 636</th>
</tr>
</thead>
<tbody render-action="loop">
<tr render-data="jsonPorts">
<td>{!Server Name!}</td>
<td>{!Destination!}</td>
<td>{!Port 53!}</td>
<td>{!Port 88!}</td>
<td>{!Port 135!}</td>
<td>{!Port 139!}</td>
<td>{!Port 389!}</td>
<td>{!Port 445!}</td>
<td>{!Port 464!}</td>
<td>{!Port 636!}</td>
</tr>
</tbody>
</table>
and here is my code:
function sortTable(tableId, column) {
var table, rows, switching, i, x, y, shouldSwitch, dir, switchcount = 0;
table = document.getElementById(tableId);
switching = true;
dir = "asc";
while (switching) {
switching = false;
rows = table.rows;
for (i = 1; i < (rows.length - 1); i++) {
shouldSwitch = false;
x = rows[i].getElementsByTagName("td")[column];
y = rows[i + 1].getElementsByTagName("td")[column];
if (!isNaN(x.innerHTML) && !isNaN(y.innerHTML)) {
if (dir == "asc") {
if (Number(x.innerHTML) > Number(y.innerHTML)) {
shouldSwitch= true;
break;
}
} else if (dir == "desc") {
if (Number(x.innerHTML) < Number(y.innerHTML)) {
shouldSwitch= true;
break;
}
}
} else {
if (dir == "asc") {
if (x.innerHTML.toLowerCase() > y.innerHTML.toLowerCase()) {
shouldSwitch= true;
break;
}
} else if (dir == "desc") {
if (x.innerHTML.toLowerCase() < y.innerHTML.toLowerCase()) {
shouldSwitch= true;
break;
}
}
}
}
if (shouldSwitch) {
rows[i].parentNode.insertBefore(rows[i + 1], rows[i]);
switching = true;
switchcount ++;
} else {
if (switchcount == 0 && dir == "asc") {
dir = "desc";
switching = true;
}
}
}
}
let sortOrder = "asc";
The way I invoke the function is this:
document.getElementById("ServerNameHeader1").addEventListener("click", function() {
sortTable("PortsTable", 0);
});
document.getElementById("ServerNameHeader2").addEventListener("click", function() {
sortTable("PortsTable", 1);
});
I guess the function is running repeatedly, but I don't know why. The html structure is the same as the other tables
Following #Gary's advice I rewrote the function and now it works. I did some search and found out that the createDocumentFragment() method turns useful when dealing with large dataset as it is my case, so I've used it.
function sortTable(tableId, column) {
const table = document.getElementById(tableId);
const tbody = table.getElementsByTagName("tbody")[0];
const rows = Array.from(tbody.getElementsByTagName("tr"));
// Extract the data into a two-dimensional array
const rowData = rows.map((row) => {
const cells = Array.from(row.getElementsByTagName("td"));
return cells.map((cell) => cell.innerText);
});
rowData.sort((a, b) => {
const aValue = a[column];
const bValue = b[column];
if (aValue < bValue) {
return -1;
} else if (aValue > bValue) {
return 1;
} else {
return 0;
}
});
// Create a new document fragment with the sorted rows
const fragment = document.createDocumentFragment();
for (let i = 0; i < rowData.length; i++) {
const cells = rowData[i];
const row = document.createElement("tr");
for (let j = 0; j < cells.length; j++) {
const cell = document.createElement("td");
cell.innerText = cells[j];
row.appendChild(cell);
}
fragment.appendChild(row);
}
tbody.innerHTML = "";
tbody.appendChild(fragment);
}
Related
I am trying to sort the data(asc or desc) in the table. For this one I want to sort the Date of VL & Date/Time of Application whenever I click on each header. However, my current code is not doing anything everytime I click it. But when I tried it on a static data it's working.
Here's my code below:
<? var data = SpreadsheetApp
.openById('sheet ID')
.getSheetByName("VL Request")
.getDataRange()
.getValues();
var timeStamp = [0];
var rid = [1];
var ldap = [2];
var aname = [7];
var lob = [9];
var dovl = [5];
var rol = [6];
var tlapprov = [12];
var tlrem = [14];
var stat = [13];
var schedrem = [11];
var tl = [3];
var pocldap = [15];
var omldap = [16];
var userName = Session.getEffectiveUser().getUsername();
?>
<table id="vllist" class="vllist2">
<tr>
<th colspan=11>POC VIEW</th>
</tr>
<tr>
<th colspan=11>Application for Date Range: <?= [formattedStart] ?> - <?= [formattedEnd] ?></th>
</tr>
<tr>
<th style="display:none;">Request ID</th>
<th>LDAP</th>
<th>Agent Name</th>
<th>Lane</th>
<th onclick="sortTable(4)">Date of VL</th>
<th>Reason of Leave</th>
<th>POC Approval</th>
<th>POC Remarks</th>
<th>Scheduler's Approval</th>
<th>Scheduler's Remarks</th>
<th onclick="sortTable(10)">Date/Time of Application</th>
</tr>
<? for (var i = 1; i < data.length; i++) { ?>
<tr>
<? if ((data[i][tlapprov] === "Pending") && ((data[i][pocldap] === userName) || (data[i][omldap] === userName)) && (data[i][dovl] >= startDate) && (data[i][dovl] <= endDate)) { ?>
<?
var vldate = data[i][dovl];
var formattedDateVL = (vldate.getMonth()+1) + '/' + vldate.getDate() + '/' + vldate.getYear();
?>
<td class="hide">
<?= data[i][rid] ?>
</td>
<td>
<?= data[i][ldap] ?>
</td>
<td>
<?= data[i][aname] ?>
</td>
<td>
<?= data[i][lob] ?>
</td>
<td>
<?= [formattedDateVL] ?>
</td>
<td>
<?= data[i][rol] ?>
</td>
<td>
<?= data[i][tlapprov] ?>
</td>
<td>
<?= data[i][tlrem] ?>
</td>
<td>
<?= data[i][stat] ?>
</td>
<td>
<?= data[i][schedrem] ?>
</td>
<td class="lefttext">
<?= data[i][timeStamp] ?>
</td>
<? } ?>
</tr>
<? } ?>
</table>
<script>
function sortTable(n) {
var table, rows, switching, i, x, y, shouldSwitch, dir, switchcount = 0;
table = document.getElementById("vllist");
switching = true;
dir = "asc";
while (switching) {
switching = false;
rows = table.rows;
for (i = 1; i < (rows.length - 1); i++) {
shouldSwitch = false;
x = rows[i].getElementsByTagName("TD")[n];
y = rows[i + 1].getElementsByTagName("TD")[n];
if (dir == "asc") {
if (x.innerHTML.toLowerCase() > y.innerHTML.toLowerCase()) {
shouldSwitch = true;
break;
}
} else if (dir == "desc") {
if (x.innerHTML.toLowerCase() < y.innerHTML.toLowerCase()) {
shouldSwitch = true;
break;
}
}
}
if (shouldSwitch) {
rows[i].parentNode.insertBefore(rows[i + 1], rows[i]);
switching = true;
switchcount ++;
} else {
if (switchcount == 0 && dir == "asc") {
dir = "desc";
switching = true;
}
}
}
}
</script>
What I would do is call the DATA into the table as normal... Then use JS to sort the records in the HTML table.
I think what you were trying to do is sort the data alphabetically first then display the data into the table . So rather put the data as is into the table, then use a sort Table function ..
function sortTable(n) {
var table, rows, switching, i, x, y, shouldSwitch, dir, switchcount = 0;
table = document.getElementById("myTable");
switching = true;
//Set the sorting direction to ascending:
dir = "asc";
/*Make a loop that will continue until
no switching has been done:*/
while (switching) {
//start by saying: no switching is done:
switching = false;
rows = table.rows;
/*Loop through all table rows (except the
first, which contains table headers):*/
for (i = 1; i < (rows.length - 1); i++) {
//start by saying there should be no switching:
shouldSwitch = false;
/*Get the two elements you want to compare,
one from current row and one from the next:*/
x = rows[i].getElementsByTagName("TD")[n];
y = rows[i + 1].getElementsByTagName("TD")[n];
/*check if the two rows should switch place,
based on the direction, asc or desc:*/
if (dir == "asc") {
if (x.innerHTML.toLowerCase() > y.innerHTML.toLowerCase()) {
//if so, mark as a switch and break the loop:
shouldSwitch= true;
break;
}
} else if (dir == "desc") {
if (x.innerHTML.toLowerCase() < y.innerHTML.toLowerCase()) {
//if so, mark as a switch and break the loop:
shouldSwitch = true;
break;
}
}
}
if (shouldSwitch) {
/*If a switch has been marked, make the switch
and mark that a switch has been done:*/
rows[i].parentNode.insertBefore(rows[i + 1], rows[i]);
switching = true;
//Each time a switch is done, increase this count by 1:
switchcount ++;
} else {
/*If no switching has been done AND the direction is "asc",
set the direction to "desc" and run the while loop again.*/
if (switchcount == 0 && dir == "asc") {
dir = "desc";
switching = true;
}
}
}
}
table {
border-spacing: 0;
width: 100%;
border: 1px solid #ddd;
}
th {
cursor: pointer;
}
th, td {
text-align: left;
padding: 16px;
}
tr:nth-child(even) {
background-color: #f2f2f2
}
<p><strong>Click the headers to sort the table.</strong></p>
<p>The first time you click, the sorting direction is ascending (A to Z).</p>
<p>Click again, and the sorting direction will be descending (Z to A):</p>
<table id="myTable">
<tr>
<!--When a header is clicked, run the sortTable function, with a parameter, 0 for sorting by names, 1 for sorting by country:-->
<th onclick="sortTable(0)">Name</th>
<th onclick="sortTable(1)">Country</th>
</tr>
<tr>
<td>Berglunds snabbkop</td>
<td>Sweden</td>
</tr>
<tr>
<td>North/South</td>
<td>UK</td>
</tr>
<tr>
<td>Alfreds Futterkiste</td>
<td>Germany</td>
</tr>
<tr>
<td>Koniglich Essen</td>
<td>Germany</td>
</tr>
<tr>
<td>Magazzini Alimentari Riuniti</td>
<td>Italy</td>
</tr>
<tr>
<td>Paris specialites</td>
<td>France</td>
</tr>
<tr>
<td>Island Trading</td>
<td>UK</td>
</tr>
<tr>
<td>Laughing Bacchus Winecellars</td>
<td>Canada</td>
</tr>
</table>
Here's a simple solution for being able to sort in a spreadsheet by clicking on check boxes above the the header row.
function onEdit(e) {
var sh=e.range.getSheet();
if(sh.getName()=='Sheet1') {
if(e.range.rowStart==1 && e.range.columnStart<=sh.getLastColumn()) {
sh.getRange(3,1,sh.getLastRow()-1,sh.getLastColumn()).sort({column:e.range.columnStart,asc:(e.value=="TRUE")?true:false});
}
}
}
Here's what the Spreadsheet Might look like:
According to the Best Practices for UI with Apps Script, you should load your data asynchronously.
To show you how to do this + to create a proper sorting function for you, I have created the following sample Sheet:
Code.gs
function createSidebarWithHTML() {
var html = HtmlService.createHtmlOutputFromFile('Page')
.setTitle('My custom sidebar')
.setWidth(300);
SpreadsheetApp.getUi().showSidebar(html);
}
function getInitialData() {
var result = SpreadsheetApp.getActiveSpreadsheet().getDataRange().getValues();
result.shift(); //Removes header row from results
//Returns an array of arrays [rows, rows, ...] where rows = [column, column, column, ...]
return result;
}
Page.html
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body>
<table id='mytable'>
<thead>
<tr>
<th colspan=4>POC VIEW</th>
</tr>
<tr>
<th colspan=4>Sample data!</th>
</tr>
<tr>
<th onclick=sort(0)>Header 1</th>
<th onclick=sort(1)>Header 2</th>
<th onclick=sort(2)>Header 3</th>
<th onclick=sort(3)>Header 4</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
<script>
var loadedData = null;
function loadTable(data) {
loadedData = data; //For caching of the received data.
var tablebody = document.querySelector('#mytable > tbody');
for (var i=0; i<data.length; i++) {
var newRow = document.createElement("tr");
for (var j=0; j<data[i].length; j++) {
var newCell = document.createElement("td");
var cellContent = document.createTextNode(data[i][j]);
newCell.appendChild(cellContent);
newRow.appendChild(newCell);
}
tablebody.appendChild(newRow);
}
}
function sort(row) {
var sortedData = loadedData;
//Sort data based on row Number
sortedData.sort(function(a,b) {
return a[row]-b[row];
});
var tablebody = document.querySelector('#mytable > tbody');
tablebody.innerHTML = ''; //Clear table body
for (var i=0; i<sortedData.length; i++) {
var newRow = document.createElement("tr");
for (var j=0; j<sortedData[i].length; j++) {
var newCell = document.createElement("td");
var cellContent = document.createTextNode(sortedData[i][j]);
newCell.appendChild(cellContent);
newRow.appendChild(newCell);
}
tablebody.appendChild(newRow);
}
}
//initialize data
function initialize() {
google.script.run.withSuccessHandler(loadTable).getInitialData();
}
initialize();
</script>
</body>
</html>
I am trying to set up a HTML table that when you click on the column header it will sort the table by that column. My current solution works perfectly for the first column, its something I found on W3schools.
Edit: (Issue was the script comparing all the contained HTML in each TD, not just the displayed text)
But when I tried to generalise it with "n" instead of leaving a number in the java script for which column to sort, I can't seem to get it to take the correct value for n.
My plan was to use the code from W3schools (below) for sorting the first column, but insert a variable n to allow the same piece of code to sort multiple columns. I am trying to get the Java script to read n from the HTML section itself but i am not sure why it's not working.
This is my version of the W3schools code where I have tried to use n, this currently sorts the first column no matter which column header I click on.
function sortTable(n) {
var table, rows, switching, i, x, y, shouldSwitch, dir, switchcount = 0;
table = document.getElementById("mytable");
switching = true;
dir = "asc";
while (switching) {
switching = false;
rows = table.rows;
for (i = 1; i < (rows.length - 1); i++) {
shouldSwitch = false;
x = rows[i].getElementsByTagName("TD")[n];
y = rows[i + 1].getElementsByTagName("TD")[n];
if (dir == "asc") {
if (x.innerHTML.toUpperCase() > y.innerHTML.toUpperCase()) {
shouldSwitch = true;
break;
}
} else if (dir == "desc") {
if (x.innerHTML.toUpperCase() < y.innerHTML.toUpperCase()) {
shouldSwitch = true;
break;
}
}
}
if (shouldSwitch) {
rows[i].parentNode.insertBefore(rows[i + 1], rows[i]);
switching = true;
switchcount ++;
} else {
if (switchcount == 0 && dir == "asc") {
dir = "desc";
switching = true;
}
}
}
}
<table id="mytable">
<tr>
<th>
<div onclick="sortTable(0)">a</div>
</th>
<th>
<div onclick="sortTable(1)">b</div>
</th>
<th>
<div onclick="sortTable(2)">c</div>
</th>
</tr>
<tr>
<td>04718J00065</td>
<td>2100305513</td>
<td>10</td>
</tr>
<tr>
<td>29417J01131</td>
<td>2100305513</td>
<td>30</td>
</tr>
<tr>
<td>07416J01979</td>
<td>2100029648</td>
<td>0</td>
</tr>
</table>
I think what the issue is, is that the Java script isn't getting a value for n and it's defaulting to 0 which is why it sorts the first column no matter which header was clicked on. How can I get it to read n=0 from onclick="sortTable(0)", n=1 from onclick="sortTable(1)" etc?
I have found out what was causing the issue.
Using
(x.innerHTML.toUpperCase() > y.innerHTML.toUpperCase())
and
x = rows[i].getElementsByTagName("TD")[n];
y = rows[i + 1].getElementsByTagName("TD")[n];
compares everything inside that TD, not just the text that gets displayed. I had removed a <a></a> section from each TD when posting the code for this question trying to avoid posting code in too much detail and the link was pointing to files that shared a name with the string that was in the first column.
<table id="mytable">
<tr>
<th>
<div onclick="sortTable(0)">a</div>
</th>
<th>
<div onclick="sortTable(1)">b</div>
</th>
<th>
<div onclick="sortTable(2)">c</div>
</th>
</tr>
<tr>
<td>04718J00065</td>
<td>2100305513</td>
<td>10</td>
</tr>
<tr>
<td>29417J01131</td>
<td>2100305513</td>
<td>30</td>
</tr>
<tr>
<td>07416J01979</td>
<td>2100029648</td>
<td>0</td>
</tr>
</table>
<script>
function sortTable(n) {
var table, rows, switching, i, x, y, shouldSwitch, dir, switchcount = 0;
table = document.getElementById("mytable");
switching = true;
dir = "asc";
while (switching) {
switching = false;
rows = table.rows;
for (i = 1; i < (rows.length - 1); i++) {
shouldSwitch = false;
x = rows[i].getElementsByTagName("A")[n];
y = rows[i + 1].getElementsByTagName("A")[n];
if (dir == "asc") {
if (x.innerHTML.toUpperCase() > y.innerHTML.toUpperCase()) {
shouldSwitch = true;
break;
}
} else if (dir == "desc") {
if (x.innerHTML.toUpperCase() < y.innerHTML.toUpperCase()) {
shouldSwitch = true;
break;
}
}
}
if (shouldSwitch) {
rows[i].parentNode.insertBefore(rows[i + 1], rows[i]);
switching = true;
switchcount ++;
} else {
if (switchcount == 0 && dir == "asc") {
dir = "desc";
switching = true;
}
}
}
}
</script>
My solution to this was just to swap "TD" in the script to "A" so it would compare inside the tag and not the link location itself. My changes to include "n" had worked as intended.
I'm trying to sort a table by course number, but I need to use a substring to sort by number. The course name looks like CX-001 for example, I wanting to ignore the first three characters. I'm using Vanilla Javascript. I'm not sure where to apply the substring, but I know I got it wrong.
function sortSubNum(subNum) {
var switchcount = 0;
var table = document.getElementById("myTable2").substring(2);
var switching = true;
// Set the sorting direction to ascending:
var dir = "asc";
/* Make a loop that will continue until
no switching has been done: */
while (switching) {
switching = false;
var rows = table.rows;
var shouldSwitch;
for (var i = 1; i < (rows.length - 1); i++) {
var x = rows[i].getElementsByTagName("TD")[subNum];
var y = rows[i + 1].getElementsByTagName("TD")[subNum];
//var resX = x.substring(2);
//var resY = y.substring(2);
if (dir === "asc") {
if (Number(x.textContent.substring(2)) < Number(y.textContent.substring(2))) {
//if so, mark as a switch and break the loop:
shouldSwitch = true;
break;
}
} else if (dir === "desc") {
if (Number(x.textContent.substring(2)) > Number(y.textContent.substring(2))) {
shouldSwitch = true;
break;
}
}
}
if (shouldSwitch) {
rows[i].parentNode.insertBefore(rows[i + 1], rows[i]);
switching = true;
switchcount++;
} else {
if (switchcount === 0 && dir === "asc") {
dir = "desc";
switching = true;
}
}
}
}
sortSubNum(1);
<table id="myTable2">
<thead>
<tr>
<th>Teacher</th>
<th>Course Number</th>
</tr>
</thead>
<tbody>
<tr>
<td>Smith</td>
<td>CS-301</td>
</tr>
<tr>
<td>Kelly</td>
<td>CX-201</td>
</tr>
<tr>
<td>Park</td>
<td>CS-001</td>
</tr>
</tbody>
</table>
Although you already found problem in your code but my solution is based on #Patrick Roberts suggestion given in comment.
Instead of implementing your own sorting method directly on the DOM, consider initializing an Array of elements and calling Array.prototype.sort(), then reinserting the newly ordered elements back into the table. It would be a lot faster and less error-prone
function sortTable(tbody, col, asc){
var rows = tbody.rows;
var rowsLen = tbody.rows.length;
var arr = new Array();
var i, j, cells, cellLen;
// fill the array with values from the table
for(i = 0; i < rowsLen; i++){
cells = rows[i].cells;
cellLen = cells.length;
arr[i] = new Array();
for(j = 0; j < cellLen; j++){
arr[i][j] = cells[j].innerHTML;
}
}
//short the array
arr.sort(function(a, b){
//this is your use case.sort the data in array after spilt.
var aCol=a[col].split("-")[1];
var bCol=b[col].split("-")[1];
return (aCol == bCol) ? 0 : ((aCol > bCol) ? asc : -1*asc);
});
for(i = 0; i < rowsLen; i++){
arr[i] = "<td>"+arr[i].join("</td><td>")+"</td>";
}
tbody.innerHTML = "<tr>"+arr.join("</tr><tr>")+"</tr>";
}
var tbody=document.getElementById("myTable2Tbody");
sortTable(tbody,1, 1);
//for asc use 1,for dsc use -1
<table id ="myTable2">
<thead>
<tr>
<th>Teacher</th>
<th>Course Number</th>
</tr>
</thead>
<tbody id ="myTable2Tbody">
<tr>
<td>Smith</td>
<td>CS-301</td>
</tr>
<tr>
<td>Kelly</td>
<td>CX-201</td>
</tr>
<tr>
<td>Park</td>
<td>CS-001</td>
</tr>
</tbody>
</table>
Check out this solution using Array.sort
var tbody = document.querySelector('#myTable2 tbody')
var trs = tbody.querySelectorAll('tr')
var sorted = [...trs].sort((tra, trb) => {
var courseA = tra.querySelectorAll('td')[1].innerText
var courseB = trb.querySelectorAll('td')[1].innerText
return courseA.split('-')[1] - courseB.split('-')[1]
})
tbody.innerHTML = '';
sorted.forEach(tr => tbody.appendChild(tr))
<table id="myTable2">
<thead>
<tr>
<th>Teacher</th>
<th>Course Number</th>
</tr>
</thead>
<tbody>
<tr>
<td>Smith</td>
<td>CS-301</td>
</tr>
<tr>
<td>Kelly</td>
<td>CX-201</td>
</tr>
<tr>
<td>Park</td>
<td>CS-001</td>
</tr>
</tbody>
</table>
Removed substring from var table. Also, I changed conditionals in the for loop from
if (Number(x.textContent.substring(2)) < Number(y.textContent.substring(2)))
to
if(x.textContent.substring(2) < y.textContent.substring(2))
function sortSubNum(subNum) {
var switchcount = 0;
var table = document.getElementById("myTable2");
var switching = true;
// Set the sorting direction to ascending:
var dir = "asc";
/* Make a loop that will continue until
no switching has been done: */
while(switching){
switching = false;
var rows = table.rows;
var shouldSwitch;
for(var i = 1; i < (rows.length - 1); i ++){
var x = rows[i].getElementsByTagName("TD")[subNum];
var y = rows[i + 1].getElementsByTagName("TD")[subNum];
//var resX = x.substring(2);
//var resY = y.substring(2);
if(dir === "asc"){
if(x.textContent.substring(2) < y.textContent.substring(2)) {
//if so, mark as a switch and break the loop:
shouldSwitch = true;
break;
}
}else if(dir === "desc"){
if(x.textContent.substring(2) > y.textContent.substring(2)){
shouldSwitch = true;
break;
}
}
}
if(shouldSwitch){
rows[i].parentNode.insertBefore(rows[i + 1], rows[i]);
switching = true;
switchcount++;
}else{
if (switchcount === 0 && dir === "asc") {
dir = "desc";
switching = true;
}
}
}
}
I'm trying to take in count filtered rows from an html table when I navigate it. I can't find any clear solutions, what would be the best way to do this please ?
Here is the filtering part of the script:
function searchPokemon() {
var input, filter, found, table, tr, td, i, j;
input = document.getElementById("mySearch");
filter = input.value.toUpperCase();
table = document.getElementById("pokemons-list");
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";
}
}
}
And here the table navigation part:
var rows = document.getElementById("pokemons-list").children[1].children;
var selectedRow = 0;
document.body.onkeydown = function(e){
rows[selectedRow].style.backgroundColor = "#FFFFFF";
if(e.keyCode == 38){
selectedRow--;
} else if(e.keyCode == 40){
selectedRow++;
} else if(e.keyCode == 13){
Pokemon_ID = selectedRow + 1;
alert('Pokemon_ID = ' + Pokemon_ID);
}
if(selectedRow >= rows.length){
selectedRow = 0;
} else if(selectedRow < 0){
selectedRow = rows.length-1;
}
rows[selectedRow].style.backgroundColor = "dodgerblue";
rows[selectedRow].scrollIntoView(true);
};
Below snippet is a basic example of what you're trying to do. Run the code snippet and type 'Aa' (without the quotes). Notice when you press the down arrow you immediately get the first Aa but then have to press down 3 more times before the following line gets highlighted. The following two down arrows are really on the hidden tr elements. The elements are hidden from being show to the user but they are still within the DOM itself.
Solution:
During the onkeydown event, before you change the background color to dodgerblue, verify the current tr element does not have display none. If it does, loop through again and check the next expected result. Since you have it looping through, as long as you don't loop back to beyond where you started then you shoudn't have an infinite loop.
var searchPokemon;
var rows = document.getElementById("pokemons-list").children[1].children;
var selectedRow = 0;
document.body.onkeydown = function(e) {
rows[selectedRow].style.backgroundColor = "#FFFFFF";
if(e.keyCode == 38){
selectedRow--;
} else if(e.keyCode == 40){
selectedRow++;
} else if(e.keyCode == 13){
Pokemon_ID = selectedRow + 1;
alert('Pokemon_ID = ' + Pokemon_ID);
}
if(selectedRow >= rows.length){
selectedRow = 0;
} else if(selectedRow < 0){
selectedRow = rows.length-1;
}
rows[selectedRow].style.backgroundColor = "dodgerblue";
rows[selectedRow].scrollIntoView(true);
};
searchPokemon = function() {
var input, filter, found, table, tr, td, i, j;
input = document.getElementById("mySearch");
filter = input.value.toUpperCase();
table = document.getElementById("pokemons-list");
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";
}
}
}
<html>
<head>
</head>
<body>
<input id="mySearch" />
<button type="button" onclick="searchPokemon()">Filter</button>
<table id="pokemons-list">
<thead> </thead>
<tbody>
<tr style="background-color: rgb(255, 255, 255);">
<td>1</td>
<td>Aaa</td><td>
<img src=""></td>
</tr>
<tr style="background-color: rgb(255, 255, 255);">
<td>2</td>
<td>Aba</td><td>
<img src=""></td>
</tr>
<tr style="background-color: rgb(255, 255, 255);">
<td>3</td>
<td>Abb</td><td>
<img src=""></td>
</tr>
<tr style="background-color: rgb(255, 255, 255);">
<td>4</td>
<td>Aab</td><td>
<img src=""></td>
</tr>
</tbody>
</table>
</body>
</html>
The snippet below should be updated so allow you to do the same test, but skip the hidden tr elements.
var searchPokemon;
var rows = document.getElementById("pokemons-list").children[1].children;
var selectedRow = 0;
document.body.onkeydown = function (e) {
rows[selectedRow].style.backgroundColor = "#FFFFFF";
var startedAt;
do {
startedAt = selectedRow;
if (e.keyCode == 38) {
selectedRow--;
} else if (e.keyCode == 40) {
selectedRow++;
} else if (e.keyCode == 13) {
Pokemon_ID = selectedRow + 1;
alert('Pokemon_ID = ' + Pokemon_ID);
}
if (selectedRow >= rows.length) {
selectedRow = 0;
} else if (selectedRow < 0) {
selectedRow = rows.length - 1;
}
} while (startedAt != selectedRow && rows[selectedRow].style.display === 'none');
rows[selectedRow].style.backgroundColor = "dodgerblue";
};
searchPokemon = function () {
var input, filter, found, table, tr, td, i, j;
input = document.getElementById("mySearch");
filter = input.value.toUpperCase();
table = document.getElementById("pokemons-list");
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";
}
}
}
<html>
<head>
</head>
<body>
<input id="mySearch" />
<button type="button" onclick="searchPokemon()">Filter</button>
<table id="pokemons-list">
<thead> </thead>
<tbody>
<tr style="background-color: rgb(255, 255, 255);">
<td>1</td>
<td>Aaa</td><td>
<img src=""></td>
</tr>
<tr style="background-color: rgb(255, 255, 255);">
<td>2</td>
<td>Aba</td><td>
<img src=""></td>
</tr>
<tr style="background-color: rgb(255, 255, 255);">
<td>3</td>
<td>Abb</td><td>
<img src=""></td>
</tr>
<tr style="background-color: rgb(255, 255, 255);">
<td>4</td>
<td>Aab</td><td>
<img src=""></td>
</tr>
</tbody>
</table>
</body>
</html>
Is there any way to find the highest value of each column (in a html table) and to add a class to it using js or jQuery?
Note: the table is build with <thead> and <tbody>
The table looks like this:
<table>
<thead>
<tr>
<th class="age">age</th>
<th class="success">success</th>
<th class="weight">weight</th>
</tr>
</thead>
<tbody>
<tr>
<td>20</td>
<td>30%</td>
<td>70.5kg</td>
</tr>
<tr>
<td>30</td>
<td>40%</td>
<td>80.9kg</td>
</tr>
<tr>
<td>13</td>
<td>60%</td>
<td>20.53kg</td>
</tr>
<tr>
<td>44</td>
<td>80.44%</td>
<td>20kg</td>
</tr>
</tbody>
</table>
Codepen
FIDDLE - http://jsfiddle.net/tariqulazam/esfj9/
JAVASCRIPT
var $table = $("#mytable");
$table.find("th").each(function(columnIndex)
{
var oldValue=0, currentValue=0, $elementToMark;
var $trs = $table.find("tr");
$trs.each(function(index, element)
{
$(this).find("td:eq("+ columnIndex +")").each(function()
{
if(currentValue>oldValue)
oldValue = currentValue;
currentValue = parseFloat($(this).html());
if(currentValue > oldValue)
{
$elementToMark = $(this);
}
if(index == $trs.length-1)
{
$elementToMark.addClass("highest");
}
});
});
});
CSS
.highest{
color:Red;
}
Here's the JSFiddle I made:JSFiddle
Here's the function, using jQuery
function MarkLargest() {
var colCount = $('th').length;
var rowCount = $('tbody tr').length;
var largestVals = new Array();
for (var c = 0; c < colCount; c++) {
var values = new Array();
for (var r = 0; r < rowCount; r++) {
var value = $('tbody tr:eq(' + r + ') td:eq(' + c + ')').text();
value = value.replace("%", "").replace("kg", "");
values.push(value);
}
var largest = Math.max.apply(Math, values);
largestVals.push(largest);
$('tbody tr').each(function() {
var text = $(this).find('td:eq(' + c + ')').text();
text = text.replace("%", "").replace("kg", "");
if (text == largest) {
$(this).find('td:eq(' + c + ')').addClass("max");
}
});
}
return largestVals[column];
}
$(function() {
MarkLargest();
})
OK, my first answer only returned the highest value of a particular column. I think this is what you are looking for (in vanilla JavaScript):
HTML
<table id="mytable">
<thead>
<tr>
<th class="age">age</th>
<th class="sucess">sucess</th>
<th class="weight">weight</th>
</tr>
</thead>
<tbody>
<tr>
<td>20</td>
<td>30%</td>
<td>70.5kg</td>
</tr>
<tr>
<td>30</td>
<td>40%</td>
<td>80.9kg</td>
</tr>
<tr>
<td>13</td>
<td>60%</td>
<td>20.53kg</td>
</tr>
<tr>
<td>44</td>
<td>80.44%</td>
<td>20kg</td>
</tr>
</tbody>
</table>
JavaScript
function markColumnCeilings ( table ) {
if ( table === null ) return;
var thead = table.tHead,
tbody = table.tBodies[0],
rowCount = tbody.rows.length,
colCount = thead.rows[0].cells.length,
maxvalues = new Array( colCount ),
maxCells = new Array( colCount ),
i = rowCount - 1,
j = colCount - 1,
cell, value;
// Loops through rows/columns to get col ceilings
for ( ; i > -1 ; i-- ) {
for ( ; j > -1 ; j-- ) {
cell = tbody.rows[ i ].cells[ j ];
value = parseFloat( cell.innerHTML );
if ( value.toString() === "NaN" ) continue;
if ( value > ( maxvalues[ j ] === undefined ? -1 : maxvalues[ j ] ) ) {
maxvalues[ j ] = value;
maxCells[ j ] = i + "," + j;
}
}
j = colCount - 1;
}
// Set classes
for ( ; j > -1 ; j-- ) {
tbody.rows[ maxCells[ j ].split( "," )[ 0 ] ]
.cells[ maxCells[ j ].split( "," )[ 1 ] ]
.setAttribute( "class", "max" );
}
}
var table = document.getElementById( 'mytable' );
markColumnCeilings( table );
CSS
td.max { font-weight: bold; }
Fiddle: http://jsfiddle.net/kboucher/cH8Ya/
I have modified the function of #sbonkosky to be able to manage various tables. In my case I have various tables and it was mixing values of all them.
function GetLargestValueForColumn(table) {
let colCount = $('table:eq('+ table +') th').length;
let rowCount = $('table:eq('+ table +') tbody tr').length;
let largestVals = new Array();
for (let c = 0; c < colCount; c++) {
let values = new Array();
for (let r = 0; r < rowCount; r++) {
let value = $('table:eq('+ table +') tbody tr:eq(' + r + ') td:eq(' + c + ')').text();
value = value.replace("%", "").replace("kg", "").replace(" ", "").replace(".", "");
values.push(value);
}
let largest = Math.max.apply(Math, values);
largestVals.push(largest);
$('tbody tr').each(function() {
let text = $(this).find('td:eq(' + c + ')').text();
text = text.replace("%", "").replace("kg", "").replace(" ", "").replace(".", "");
if (text == largest) {
$(this).find('td:eq(' + c + ')').addClass("max");
}
});
}
return
}
$(function() {
$('table').each(function(table) {GetLargestValueForColumn(table)});
})