The width of the actual td is a real number, but I do not know if offsetWidth returns only integers. Are there any other properties that return the length of the cell in real number? In chrome's developer tools, press the CTRL+SHIFT+C shortcut to see the length, which is different. Can I make it easier to get to the length if I use jQuery?
function fitWidth(selector1, selector2) {
console.log('fitWidth is excuted');
let tableHeadElements = document.getElementsByClassName(selector1);
let tableBodyElements = document.getElementsByClassName(selector2);
for (let i = 0; i < tableHeadElements.length; i++) {
let tableDataOfFirstHeadTableRow = tableHeadElements[i].firstElementChild.children; // head의 첫번째 tr
let tableDataOfFirstBodyTableRow = tableBodyElements[i].firstElementChild.children; // body의 첫번째 tr
console.log("body tr count: " + tableDataOfFirstBodyTableRow.length);
for (let j = 0; j < tableDataOfFirstHeadTableRow.length; j++) {
let headCell = tableDataOfFirstHeadTableRow[j];
let bodyCell = tableDataOfFirstBodyTableRow[j];
console.log("head-cell width: " + headCell.clientWidth, "body-cell width: " + bodyCell.clientWidth);
/*
headCell.style.width = (bodyCell.offsetWidth) + "px";
headCell.style.width = (bodyCell.scrollWidth) + "px";
*/
if (headCell.clientWidth != bodyCell.clientWidth) {
headCell.clientWidth > bodyCell.clientWidth ? bodyCell.style.width = headCell.clientWidth + "px" : headCell.style.width = bodyCell.clientWidth + "px";
}
}
}
};
<!DOCTYPE HTML>
<html>
<head>
</head>
<body>
<table border="1">
<thead id="a" class="queue-head">
<tr>
<td>AppName</td>
<td>QueueName</td>
<td>Server</td>
<td>Service</td>
<td>ReceptionCount</td>
<td>ProcessingCount</td>
<td>QueueCount</td>
<td>Limit</td>
<td></td>
</tr>
</thead>
</table>
<table border="1">
<tbody id="b" class="queue-body">
<tr>
<td>ABC</td>
<td>ABCDEFGHI</td>
<td>abcd01</td>
<td>9999</td>
<td>400</td>
<td>999</td>
<td>20000</td>
<td>2000</td>
<td></td>
</tr>
<tr>
<td>ABC</td>
<td>ABCDEFGHI</td>
<td>abcd01</td>
<td>9999</td>
<td>400</td>
<td>999</td>
<td>20000</td>
<td>2000</td>
<td></td>
</tr>
</tbody>
</table>
<hr />
<table border="1">
<thead class="queue-head">
<tr>
<td>AppName</td>
<td>QueueName</td>
<td>Server</td>
<td>Service</td>
<td>ReceptionCount</td>
<td>ProcessingCount</td>
<td>QueueCount</td>
<td>Limit</td>
<td></td>
</tr>
</thead>
</table>
<table border="1">
<tbody class="queue-body">
<tr>
<td>A22</td>
<td>ABCDEFGHI</td>
<td>abcd01</td>
<td>9999</td>
<td>400</td>
<td>999</td>
<td>20000</td>
<td>2000</td>
<td></td>
</tr>
<tr>
<td>A22</td>
<td>ABCDEFGHI</td>
<td>abcd01</td>
<td>9999</td>
<td>400</td>
<td>999</td>
<td>20000</td>
<td>2000</td>
<td></td>
</tr>
</tbody>
</table>
<hr />
<button onclick='fitWidth("queue-head", "queue-body");'>fit width</button>
</body>
</html>
You should use getBoundingClientRect() which returns a non-rounded number.
Both offsetWidth and clientWidth return a rounded integer. These pages state:
Note: This property will round the value to an integer. If you need a fractional value, use element.getBoundingClientRect().
There are a couple of solutions for this.
Let's start by the issue first.
You're using the clientWidth which according to the documentation.
The clientWidth property returns the viewable width of an element in pixels, including padding, but not the border, scrollbar or margin.
So when you assign the clientWidth of an element to another's width, it adds the padding as well, which what we want but ignore the borders if we have any, that is what's happening.
Now if we remove the padding this works, if both cells has the same border width, Even if we add box-sizing: border-box; since clientWidth doesn't account for the borders there will be no difference.
Moving on to offsetWidth.
The offsetWidth property returns the viewable width of an element in pixels, including padding, border and scrollbar, but not the margin.
Now this one seems perfect since it includes the borders and the scrollbars as well, so adding box-sizing: border-box; will work.
function fitWidth(selector1, selector2) {
console.log('fitWidth is excuted');
let tableHeadElements = document.getElementsByClassName(selector1);
let tableBodyElements = document.getElementsByClassName(selector2);
for (let i = 0; i < tableHeadElements.length; i++) {
let tableDataOfFirstHeadTableRow = tableHeadElements[i].firstElementChild.children; // head의 첫번째 tr
let tableDataOfFirstBodyTableRow = tableBodyElements[i].firstElementChild.children; // body의 첫번째 tr
console.log("body tr count: " + tableDataOfFirstBodyTableRow.length);
for (let j = 0; j < tableDataOfFirstHeadTableRow.length; j++) {
let headCell = tableDataOfFirstHeadTableRow[j];
let bodyCell = tableDataOfFirstBodyTableRow[j];
console.log("head-cell width: " + headCell.offsetWidth, "body-cell width: " + bodyCell.offsetWidth);
/*
headCell.style.width = (bodyCell.offsetWidth) + "px";
headCell.style.width = (bodyCell.scrollWidth) + "px";
*/
if (headCell.offsetWidth != bodyCell.offsetWidth) {
headCell.offsetWidth > bodyCell.offsetWidth ? bodyCell.style.width = headCell.offsetWidth + "px" : headCell.style.width = bodyCell.offsetWidth + "px";
}
}
}
};
*{
box-sizing: border-box;
}
td{
max-width:100px;
white-space: nowrap;
overflow-x: scroll;
}
<table border="1">
<thead id="a" class="queue-head">
<tr>
<td>AppName</td>
<td>QueueName</td>
<td>Server</td>
<td>Service</td>
<td>ReceptionCount</td>
<td>ProcessingCount</td>
<td>QueueCount</td>
<td>Limit</td>
<td></td>
</tr>
</thead>
</table>
<table border="1">
<tbody id="b" class="queue-body">
<tr>
<td>ABC</td>
<td>ABCDEFGHI</td>
<td>abcd01</td>
<td>9999</td>
<td>400</td>
<td>999</td>
<td>20000</td>
<td>2000</td>
<td></td>
</tr>
<tr>
<td>ABC</td>
<td>ABCDEFGHI</td>
<td>abcd01</td>
<td>9999</td>
<td>400</td>
<td>999</td>
<td>20000</td>
<td>2000</td>
<td></td>
</tr>
</tbody>
</table>
<hr />
<table border="1">
<thead class="queue-head">
<tr>
<td>AppName</td>
<td>QueueName</td>
<td>Server</td>
<td>Service</td>
<td>ReceptionCount</td>
<td>ProcessingCount</td>
<td>QueueCount</td>
<td>Limit</td>
<td></td>
</tr>
</thead>
</table>
<table border="1">
<tbody class="queue-body">
<tr>
<td>A22</td>
<td>ABCDEFGHI</td>
<td>abcd01</td>
<td>9999</td>
<td>400</td>
<td>999</td>
<td>20000</td>
<td>2000</td>
<td></td>
</tr>
<tr>
<td>A22</td>
<td>ABCDEFGHI</td>
<td>abcd01</td>
<td>9999</td>
<td>400</td>
<td>999</td>
<td>20000</td>
<td>2000</td>
<td></td>
</tr>
</tbody>
</table>
<hr />
<button onclick='fitWidth("queue-head", "queue-body");'>fit width</button>
Or you can calculate the width yourself.
As pointed Here
If a div has this definition
div {
width: 320px;
padding: 10px;
border: 5px solid gray;
margin: 0;
}
it's width will be.
320px (width)
+ 20px (left + right padding)
+ 10px (left + right border)
+ 0px (left + right margin)
= 350px
Related
I am trying to add Price from table column to a total.
I am having problem adding values such as 10.00 or 5.99. I am able to calculate prices with int values, but not with values 10.00 or 5.99, etc.
Here is what I have below.
var table = document.getElementById("myTable"),
sumVal = 0;
for (var i = 1; i < table.rows.length; i++) {
sumVal = sumVal + parseF(table.rows[i].cells[2].innerHTML);
}
document.getElementById("val").innerHTML = "SubTotal =" + sumVal;
console.log(sumVal);
<table id="myTable">
<tr>
<th>Item</th>
<th>Price</th>
<th>Remove</th>
</tr>
<tr>
<td>Hoddie</td>
<td class="count-me">15.00</td>
<td><button onClick="myFunction()">Remove</button></td>
</tr>
<tr>
<td>Nike Cap</td>
<td class="count-me">10.99</td>
<td><button onClick="myFunction()">Remove</button></td>
</tr>
</table>
<span id="val"></span>
You have three issues:
You are grabbing the wrong cell index, indices start at 0:
table.rows[i].cells[1]
You need to call the correct parse function:
parseFloat(table.rows[i].cells[1].innerHTML);
You need to format your output:
"SubTotal = $" + sumVal.toFixed(2);
Update: Added functionality for removing rows.
updateSubTotal(); // Initial call
function updateSubTotal() {
var table = document.getElementById("myTable");
let subTotal = Array.from(table.rows).slice(1).reduce((total, row) => {
return total + parseFloat(row.cells[1].innerHTML);
}, 0);
document.getElementById("val").innerHTML = "SubTotal = $" + subTotal.toFixed(2);
}
function onClickRemove(deleteButton) {
let row = deleteButton.parentElement.parentElement;
row.parentNode.removeChild(row);
updateSubTotal(); // Call after delete
}
#myTable td {
padding: 0.25em;
}
#val {
display: block;
margin-top: 0.5em;
}
<table id="myTable">
<tr>
<th>Item</th>
<th>Price</th>
<th>Remove</th>
</tr>
<tr>
<td>Hoodie</td>
<td class="count-me">15.00</td>
<td><button onClick="onClickRemove(this)">Remove</button></td>
</tr>
<tr>
<td>Nike Cap</td>
<td class="count-me">10.99</td>
<td><button onClick="onClickRemove(this)">Remove</button></td>
</tr>
</table>
<span id="val"></span>
You are accessing the incorrect array element and also need to use parseFloat
The cells array is zero-based so you need to use cells[1] to access the second column:
var table = document.getElementById("myTable"),
sumVal = 0;
for (var i = 1; i < table.rows.length; i++) {
sumVal = sumVal + parseFloat(table.rows[i].cells[1].innerHTML);
}
document.getElementById("val").innerHTML = "SubTotal =" + sumVal;
console.log(sumVal);
<table id="myTable">
<tr>
<th>Item</th>
<th>Price</th>
<th>Remove</th>
</tr>
<tr>
<td>Hoddie</td>
<td class="count-me">15.00</td>
<td><button onClick="myFunction()">Remove</button></td>
</tr>
<tr>
<td>Nike Cap</td>
<td class="count-me">10.99</td>
<td><button onClick="myFunction()">Remove</button></td>
</tr>
</table>
<span id="val"></span>
updateSubTotal(); // Initial call
function updateSubTotal() {
var table = document.getElementById("myTable");
let subTotal = Array.from(table.rows).slice(1).reduce((total, row) => {
return total + parseFloat(row.cells[1].innerHTML);
}, 0);
let subTotal2 = Array.from(table.rows).slice(1).reduce((total, row) => {
return total + parseFloat(row.cells[2].innerHTML);
}, 0);
document.getElementById("val").innerHTML = "SubTotal = $" + subTotal.toFixed(2);
document.getElementById("val1").innerHTML = subTotal2.toFixed(2);
}
function onClickRemove(deleteButton) {
let row = deleteButton.parentElement.parentElement;
row.parentNode.removeChild(row);
updateSubTotal(); // Call after delete
}
#myTable td {
padding: 0.25em;
}
#val {
display: block;
margin-top: 0.5em;
}
<table id="myTable">
<tr>
<th>Item</th>
<th>Price</th>
<th>M2</th>
<th>Remove</th>
</tr>
<tr>
<td>Hoodie</td>
<td class="count-me">15.00</td>
<td class="count-me">34.00</th>
<td><button onClick="onClickRemove(this)">Remove</button></td>
</tr>
<tr>
<td>Nike Cap</td>
<td class="count-me">10.99</td>
<td class="count-me">22.34</th>
<td><button onClick="onClickRemove(this)">Remove</button></td>
</tr>
</table>
<span id="val"></span>
<span id="val1"></span>
var cell = document.getElementsByClassName("count-me");
var val = 0;
var i = 0;
while (cell[i] != undefined) {
val += parseFloat(cell[i].innerHTML);
i++;
} //end while
document.getElementById("val").innerHTML = parseFloat(val).toFixed(2);
console.log(parseFloat(val).toFixed(2));
<table id="myTable">
<tr>
<th>Item</th>
<th>Price</th>
<th>Remove</th>
</tr>
<tr id="">
<td>Hoddie</td>
<td class="count-me">15.00</td>
<td>
<button onClick="myFunction()">Remove</button>
</td>
</tr>
<tr>
<td>Nike Cap</td>
<td class="count-me">10.99</td>
<td>
<button onClick="myFunction()">Remove</button>
</td>
</tr>
</table>
<span id="val"></span>
How can I fix the complete thead when scrolling in Y-Direction?
It's important to note that the table width is greater than the wrapping div. So scrolling in X-Direction should also scroll the thead - so separating thead from table is not an option I guess.
Please see this fiddle
HTML
<div>
<table>
<thead>
<tr>
<th>
One
</th>
<th>
Two
</th>
</tr>
</thead>
<tbody>
<tr>
<td>
DataOne
</td>
<td>
DatTwo
</td>
</tr>
<tr>
<td>
DataOne
</td>
<td>
DatTwo
</td>
</tr>
<tr>
<td>
DataOne
</td>
<td>
DatTwo
</td>
</tr>
<tr>
<td>
DataOne
</td>
<td>
DatTwo
</td>
</tr>
</tbody>
</table>
</div>
CSS
div {
width:80px;
height:100px;
}
table {
height:100px;
display:block;
overflow:auto;
}
A solution would be to assign a function to the scroll event handler using javascript. Use a second table, and if table1 isn't scrolled downwards, have table 2 hidden. When table1 is scrolled downwards call .show() on table2's .
Note: you will need to include JQuery in your HTML.
HTML
<table id="table-1">
<thead>
<tr>
<th>Col1</th>
<th>Col2</th>
<th>Col3</th>
</tr>
</thead>
<tbody>
<tr>
<td>info</td>
<td>info</td>
<td>info</td>
</tr>
<tr>
<td>info</td>
<td>info</td>
<td>info</td>
</tr>
<tr>
<td>info</td>
<td>info</td>
<td>info</td>
</tr>
</tbody>
CSS
body { height: 1000px; }
#header-fixed {
position: fixed;
top: 0px; display:none;
background-color:white;
}
Javascript/JQuery
var tableOffset = $("#table-1").offset().top;
var $header = $("#table-1 > thead").clone();
var $fixedHeader = $("#header-fixed").append($header);
$(window).bind("scroll", function() {
var offset = $(this).scrollTop();
if (offset >= tableOffset && $fixedHeader.is(":hidden")) {
$fixedHeader.show();
}
else if (offset < tableOffset) {
$fixedHeader.hide();
}
});
Fiddle:
http://jsfiddle.net/py8b0s7v/
th{
position: sticky;
top: 0;
background-color:white;
}
Hi,
Add above Css.
Set position as sticky instead of fixed.
Fixed will "fix" the component to the top of the screen no matter where it is on the page. Sticky uses JS to calculate where it is on the page and only fix to the top when the viewport reaches it. Simply put, use sticky if you want content above the component.
This solved it for me:
document.getElementById("TableHead").addEventListener("scroll", function(){
var translate = "translate(0,"+this.scrollTop+"px)";
this.querySelector("thead").style.transform = translate;
});
Fiddle: https://jsfiddle.net/pxtokb4g/97/
The td:nth-child('n') is not working over in my table it gives null in the log Where as it is working when i use children[n] it is a simple function for searching
I couldn't find the reason why it is giving out a null.. Here is the code
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Table Searching</title>
<style>
th{
font-weight: bolder;
}
table, th, td{
font-size: 20px;
border: 2px solid black;
border-collapse: collapse;
}
</style>
</head>
<body>
<input type="text" name="search">
<button class="s" onclick="Search()">Search by Name</button>
<button class="s" onclick="Search()">Search by Country</button>
<button class="s" onclick="Search()">Search by Pet</button>
<table>
<tr>
<th>Name</th>
<th>Country</th>
<th>Pet</th>
</tr>
<tr>
<td>Abhi</td>
<td>Canada</td>
<td>koala</td>
</tr>
<tr>
<td>Riya</td>
<td>France</td>
<td>Parrot</td>
</tr>
<tr>
<td>Sid</td>
<td>UK</td>
<td>Pig</td>
</tr>
<tr>
<td>Kritika</td>
<td>Germany</td>
<td>Cat</td>
</tr>
<tr>
<td>Kartik</td>
<td>China</td>
<td>Frog</td>
</tr>
<tr>
<td>Radhika</td>
<td>India</td>
<td>Dog</td>
</tr>
</table>
<script>
var input=document.getElementsByName("search")[0];
var s=document.getElementsByClassName("s");
var n=0;
function Search(){
for (var j=0; j<s.length; j++)
{
console.log("element");
console.log(s[j]);
console.log("target");
console.log(event.target);
if(s[j]==event.target){
n=j;
console.log(n);
}
}
var val= input.value;
var a=document.querySelectorAll("table > tbody > tr");
console.log(a);
for(var i =0; i<a.length; i++)
{
var d = a[i].querySelector('td:nth-child("+n+")');
console.log(d);
if(d.innerHTML.toLowerCase()==val.toLowerCase()){
a[i].style.display="";
}
else
{
a[i].style.display="none";
}
}
}
</script>
</body>
</html>
Here are the three reasons why you are giving out null in your code:
First, as stated by Satpal, this code 'td:nth-child("+n+")' will not replace n by its value. It's like writing td:nth-child("+n+") in css.
The solution for this is to write: 'td:nth-child(' + n + ')'. You then concatenate the value of n with the rest of the string
The value of n is an index in a array, so it starts at 0 and ends at array.length - 1. The problem is that the nth-child selector actually selects the nth-child (brilliant naming), so if n is 0 (in the case of searching by name), you'll try to select the 0th-child, wihich doesn't exist... You then have to write: 'td:nth-child(' + (n + 1) + ')' or change the definition of n
You have no <tbody> tag in your HTML. Which means that all the content of the table will be wrapped in a tbody and your selector document.querySelectorAll("table > tbody > tr")will also selects the header of your table. To avoid that, change your HTML accordingly.
Something like that:
<table>
<thead>
<tr>
<th>Name</th>
<th>Country</th>
<th>Pet</th>
</tr>
</thead>
<tbody>
<tr>
<td>Abhi</td>
<td>Canada</td>
<td>koala</td>
</tr>
<tr>
<td>Riya</td>
<td>France</td>
<td>Parrot</td>
</tr>
<tr>
<td>Sid</td>
<td>UK</td>
<td>Pig</td>
</tr>
<tr>
<td>Kritika</td>
<td>Germany</td>
<td>Cat</td>
</tr>
<tr>
<td>Kartik</td>
<td>China</td>
<td>Frog</td>
</tr>
<tr>
<td>Radhika</td>
<td>India</td>
<td>Dog</td>
</tr>
</tbody>
</table>
Here is a jsfiddle where the search works fine: https://jsfiddle.net/n23685b6/1/
Hope this helps!
scrollToNthChild = (): * => {
var tableBody = document.getElementById('event-table-body');
var tableRows = tableBody.getElementsByTagName('tr');
let targetElement = tableRows[7];
targetElement.scrollIntoView();
}
I'm trying to write an efficient javascript function that will loop through a table, grab all numbers, and ignore all tds with strings. The columns will be added and averaged, and rows will be appended for each.
I have the basic functionality for this working. Whereas, if the table does not include a string, the results are as expected. When the table does include a string, the total and average of the column are way off and I'm not exactly sure how the answer is being calculated. I'm hoping somebody can help me figure out a way to ignore these values all together, and create a more efficient way of writing this function.
Finally, I want to be able to call this function by simply passing in the table, e.g. buildRows(table);
Here's what I got so far:
// function to build total and average rows
function buildRow($element) {
var result = [];
$($element).find('tbody tr').each(function() {
// Ignore the first column reserved for labels
$('td:not(:first)', this).each(function(index, val) {
if (!result[index]) result[index] = 0;
result[index] += parseInt($(val).text());
});
});
// Get the total amount rows
var rowCount = $($element).find('tbody tr').length;
// Add Average Row
$($element).append('<tr class="avg-row"></tr>');
$($element).find('tr').last().append('<td>' + 'Averages' + '</td>');
$(result).each(function() {
$($element).find('tr').last().append('<td class="avg-td">' + this / rowCount + '</td>');
});
// Add Total Row
$($element).append('<tr class="total-row"></tr>');
$($element).find('tr').last().append('<td>' + 'Totals' + '</td>');
$(result).each(function() {
$($element).find('tr').last().append('<td class="total-td">' + this + '</td>');
});
}
// ideal function calls
var tableOne = $('.tableOne');
buildRow(tableOne);
var tableTwo = $('.tableTwo');
buildRow(tableTwo);
table {
border: 1px solid #333;
padding: 10px;
margin-right: 5px;
}
table tr:nth-child(odd) {
background-color: #e0e0e0;
}
table td {
padding: 10px;
border: 1px solid #555;
text-align: center;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<table class="tableOne">
<tr>
<td>Row One</td>
<td>45</td>
<td>23.356</td>
<td>88</td>
</tr>
<tr>
<td>Row Two</td>
<td>111440.568</td>
<td>115555</td>
<td>4.21598</td>
</tr>
<tr>
<td>Row Three</td>
<td>27</td>
<td>42</td>
<td>144487.11</td>
</tr>
<tr>
<td>Row Four</td>
<td>23.356</td>
<td>125%</td>
<td>778978523.36</td>
</tr>
</table>
<table class="tableTwo">
<tr>
<td>Row One</td>
<td>45</td>
<td>23.356</td>
<td>Hello</td>
</tr>
<tr>
<td>Row Two</td>
<td>111440.568</td>
<td>115555</td>
<td>4.21598</td>
</tr>
<tr>
<td>Row Three</td>
<td>Dog</td>
<td>true</td>
<td>144487.11</td>
</tr>
<tr>
<td>Row Four</td>
<td>23.356</td>
<td>125%</td>
<td>778978523.36</td>
</tr>
</table>
The first table with no strings seems okay, 2nd table the results are way off and I'm not sure how the totals are being calculated.
You cannot just do parseInt on every value, it sometimes transform a string to a weird number. You have to check if it is not a valid number before you sum it up. I think of a regex or something like that, untested.
var v = $(val).text();
if(v.match(/^[\s0-9\.]+$/)){}
You need to check whether td value has integer or not by using var reg = /^\d+$/;
Since your using jQuery why not just use jQuery's built in is numeric condition check function $.isNumeric which will parse out pretty much all the data you don't want.
// true (numeric)
$.isNumeric( "-10" )
$.isNumeric( "0" )
$.isNumeric( 0xFF )
$.isNumeric( "0xFF" )
$.isNumeric( "8e5" )
$.isNumeric( "3.1415" )
$.isNumeric( +10 )
$.isNumeric( 0144 )
// false (non-numeric)
$.isNumeric( "-0x42" )
$.isNumeric( "7.2acdgs" )
$.isNumeric( "" )
$.isNumeric( {} )
$.isNumeric( NaN )
$.isNumeric( null )
$.isNumeric( true )
$.isNumeric( Infinity )
$.isNumeric( undefined )
Source: https://api.jquery.com/jQuery.isNumeric/
Example: https://jsfiddle.net/1d9Lbnp1/1/
You could just ignore the Nans and use Number instead of parseInt if you want something more accurate:
$('td:not(:first)', this).each(function(index, val) {
if (!result[index]) result[index] = 0;
var num = Number($(val).text());
num = isNaN(num) ? 0 : num;
result[index] += num;
});
Two things to remember:
parseFloat will capture floats and integers
When working with numbers, you can use the ||0 to hack ignore the falsy values, replacing them with a 0
Other than that, I took some liberties with your code, putting the necessary elements in variables:
// function to build total and average rows
function buildRow($table) {
var result = [],
$tbody = $table.find('tbody'),
$rows = $tbody.find('tr'),
row_count = $rows.length;
$rows.each(function() {
// Ignore the first column reserved for labels
var $cells = $('td:not(:first)',this);
$cells.each(function(index,cell) {
if (!result[index])
result[index] = 0;
result[index] += parseFloat($(cell).text()||0);
});
});
// Add Average Row
var $avg_row = $('<tr class="avg-row"></tr>');
$avg_row.append('<td>Averages</td>');
$.each(result,function() {
$avg_row.append('<td class="avg-td">' + ( this / row_count ) + '</td>');
});
$tbody.append($avg_row);
// Add Total Row
var $total_row = $('<tr class="total-row"></tr>');
$total_row.append('<td>Totals</td>');
$.each(result,function() {
$total_row.append('<td class="total-td">' + this + '</td>');
});
$tbody.append($total_row);
}
// ideal function calls
var $tableOne = $('.tableOne');
buildRow($tableOne);
var $tableTwo = $('.tableTwo');
buildRow($tableTwo);
table {
border: 1px solid #333;
padding: 10px;
margin-right: 5px;
}
table tr:nth-child(odd) {
background-color: #e0e0e0;
}
table td {
padding: 10px;
border: 1px solid #555;
text-align: center;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<table class="tableOne">
<tr>
<td>Row One</td>
<td>45</td>
<td>23.356</td>
<td>88</td>
</tr>
<tr>
<td>Row Two</td>
<td>111440.568</td>
<td>115555</td>
<td>4.21598</td>
</tr>
<tr>
<td>Row Three</td>
<td>27</td>
<td>42</td>
<td>144487.11</td>
</tr>
<tr>
<td>Row Four</td>
<td>23.356</td>
<td>125%</td>
<td>778978523.36</td>
</tr>
</table>
<table class="tableTwo">
<tr>
<td>Row One</td>
<td>45</td>
<td>23.356</td>
<td>Hello</td>
</tr>
<tr>
<td>Row Two</td>
<td>111440.568</td>
<td>115555</td>
<td>4.21598</td>
</tr>
<tr>
<td>Row Three</td>
<td>Dog</td>
<td>true</td>
<td>144487.11</td>
</tr>
<tr>
<td>Row Four</td>
<td>23.356</td>
<td>125%</td>
<td>778978523.36</td>
</tr>
</table>
Here's another way to do the same thing, by retrieving the values first by column (using nth-child) and then using reduce to calculate sums:
['.tableOne', '.tableTwo'].forEach(function(tbl){
makeAggregates($(tbl));
});
function makeAggregates($tbl) {
var $tbody = $tbl.find('tbody');
var sums = sumOfColumns($tbody);
var $total_row = $('<tr class="total-row"><td class="total-td">Total</td></tr>');
var $average_row = $('<tr class="avg-row"><td class="avg-td">Averages</td></tr>');
$.each(sums, function(key,col) {
var total = col[1],
items = col[0];
$total_row.append('<td class="total-row">' + total + '</td>');
$average_row.append('<td class="avg-row">' + total / items + '</td>');
});
$tbody.append($average_row,$total_row);
}
function sumOfColumns($tbody) {
var $rows = $tbody.find('tr');
// Get number of columns
var num_cols = $rows.first().find('td').length;
var col_sums = {};
for (var col = 2; col < num_cols + 1; col++) {
var $col_data = $tbody.find('td:nth-child(' + col + ')'),
arr_values = $col_data.map(function() {
return this.textContent
}).get();
col_sums[col - 1] = [
arr_values.length,
arr_values.reduce(function(pv, cv) {
return pv + (parseFloat(cv) || 0)
}, 0)
];
}
return col_sums;
}
table {
border: 1px solid #333;
padding: 10px;
margin-right: 5px;
}
table tr:nth-child(odd) {
background-color: #e0e0e0;
}
table td {
padding: 10px;
border: 1px solid #555;
text-align: center;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<table class="tableOne">
<tr>
<td>Row One</td>
<td>45</td>
<td>23.356</td>
<td>88</td>
</tr>
<tr>
<td>Row Two</td>
<td>111440.568</td>
<td>115555</td>
<td>4.21598</td>
</tr>
<tr>
<td>Row Three</td>
<td>27</td>
<td>42</td>
<td>144487.11</td>
</tr>
<tr>
<td>Row Four</td>
<td>23.356</td>
<td>125%</td>
<td>778978523.36</td>
</tr>
</table>
<table class="tableTwo">
<tr>
<td>Row One</td>
<td>45</td>
<td>23.356</td>
<td>Hello</td>
</tr>
<tr>
<td>Row Two</td>
<td>111440.568</td>
<td>115555</td>
<td>4.21598</td>
</tr>
<tr>
<td>Row Three</td>
<td>Dog</td>
<td>true</td>
<td>144487.11</td>
</tr>
<tr>
<td>Row Four</td>
<td>23.356</td>
<td>125%</td>
<td>778978523.36</td>
</tr>
</table>
As others have mentioned, the trick is to check if the value is a number before using it in your calculations. That's as easy as isNaN(parseInt(string)).
Overall I think you can simplify your code a lot by extracting the (good) data from the table into arrays of numbers (one array for each column). You can see that in the getData function in the below snippet. Then it's a simple matter of iterating over each column's data to calculate its total and average.
You can also simplify things by using semantic markup: <tbody> for your data, <th> for heading cells, and <tfoot> for the Averages and Totals rows. This makes both extracting the data and styling the output much easier.
Here's how it looks with jQuery. Mind you, my jQuery's a bit rusty. At the end of the post you'll find another snippet sans jQuery, which is only slightly more verbose.
jQuery
function getData($rows) {
return $('td', $rows[0]).toArray().map((_, idx) =>
$rows.toArray().reduce((result, row) => {
const value = parseInt($('td', row)[idx].innerText, 10);
return result.concat(isNaN(value) ? [] : value);
}, [])
);
}
function generateRow(label, values, precision=3) {
const mult = Math.pow(10, precision);
return $(`<tr><th>${label}</th></tr>`).append(
values.map(val => $(`<td>${Math.round(val * mult) / mult}</td>`)));
}
function aggregateData($table) {
const averages = [];
const totals = [];
getData($table.find('tbody tr')).forEach(values => {
const total = values.reduce((total, val) => (total + val), 0);
totals.push(total);
averages.push(total / values.length);
});
$('<tfoot>').append(
generateRow('Averages', averages),
generateRow('Totals', totals)
).appendTo($table)
}
aggregateData($('.tableOne'));
aggregateData($('.tableTwo'));
table { width: 300px; margin-bottom: 1em; border: 1px solid gray; }
tbody tr:nth-child(odd) { background-color: #e0e0e0; }
td, th { padding: 7px; text-align: center; }
tfoot tr { background-color: paleturquoise; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<table class="tableOne">
<tbody>
<tr>
<th>One</th>
<td>10</td>
<td>300</td>
<td>6000</td>
</tr>
<tr>
<th>Two</th>
<td>20</td>
<td>400</td>
<td>7000</td>
</tr>
<tr>
<th>Three</th>
<td>30</td>
<td>500</td>
<td>8000</td>
</tr>
<tr>
<th>Four</th>
<td>40</td>
<td>600</td>
<td>9000</td>
</tr>
</tbody>
</table>
<table class="tableTwo">
<tr>
<th>One</th>
<td>10</td>
<td>300</td>
<td>Hello</td>
</tr>
<tr>
<th>Two</th>
<td>20</td>
<td>400</td>
<td>7000</td>
</tr>
<tr>
<th>Three</th>
<td>Dog</td>
<td>true</td>
<td>8000</td>
</tr>
<tr>
<th>Four</th>
<td>40</td>
<td>600</td>
<td>9000</td>
</tr>
</table>
No jQuery
function getData(rows) {
const numCols = rows[0].querySelectorAll('td').length;
return new Array(numCols).fill().map((_, idx) =>
Array.prototype.reduce.call(rows, (result, row) => {
const value = parseInt(row.querySelectorAll('td')[idx].innerText, 10);
return result.concat(isNaN(value) ? [] : value);
}, [])
);
}
function generateRow(label, values, precision=3) {
const mult = Math.pow(10, precision);
const row = document.createElement('tr');
row.innerHTML = values.reduce((html, val) => html + `<td>${Math.round(val * mult) / mult}</td>`, `<th>${label}</th>`);
return row;
}
function aggregateData(table) {
const totals = [];
const averages = [];
getData(table.querySelectorAll('tbody tr')).forEach(values => {
const total = values.reduce((total, val) => (total + val), 0);
totals.push(total);
averages.push(total / values.length);
});
const tfoot = document.createElement('tfoot');
tfoot.appendChild(generateRow('Averages', averages));
tfoot.appendChild(generateRow('Totals', totals));;
table.appendChild(tfoot);
}
aggregateData(document.querySelector('.tableOne'));
aggregateData(document.querySelector('.tableTwo'));
table { width: 300px; margin-bottom: 1em; border: 1px solid gray; }
tbody tr:nth-child(odd) { background-color: #e0e0e0; }
td, th { padding: 7px; text-align: center; }
tfoot tr { background-color: paleturquoise; }
<table class="tableOne">
<tbody>
<tr>
<th>One</th>
<td>10</td>
<td>300</td>
<td>6000</td>
</tr>
<tr>
<th>Two</th>
<td>20</td>
<td>400</td>
<td>7000</td>
</tr>
<tr>
<th>Three</th>
<td>30</td>
<td>500</td>
<td>8000</td>
</tr>
<tr>
<th>Four</th>
<td>40</td>
<td>600</td>
<td>9000</td>
</tr>
</tbody>
</table>
<table class="tableTwo">
<tr>
<th>One</th>
<td>10</td>
<td>300</td>
<td>Hello</td>
</tr>
<tr>
<th>Two</th>
<td>20</td>
<td>400</td>
<td>7000</td>
</tr>
<tr>
<th>Three</th>
<td>Dog</td>
<td>true</td>
<td>8000</td>
</tr>
<tr>
<th>Four</th>
<td>40</td>
<td>600</td>
<td>9000</td>
</tr>
</table>
So I'm trying to figure out the best and easiest way to highlight a selection of cells from a table.
#A1lnk, #B1lnk {cursor: pointer;}
<table border="1">
<tr>
<th colspan="2"><a id='A1lnk'>A1</a></th><th colspan="2"><a id='B1lnk'>B1</a></th>
</tr>
<tr>
<td>A1-1</td><td>A1-2</td><td>B1-1</td><td>B1-2</td>
</tr>
<tr>
<td>A1-3</td><td>A1-4</td><td>B1-3</td><td>B1-4</td>
</tr>
<tr>
<td>A1-5</td><td>A1-6</td><td>B1-5</td><td>B1-6</td>
</tr>
<tr>
<th colspan="2"><a id='C1lnk'>C1</a></th><th colspan="2"><a id='D1lnk'>D1</a></th>
</tr>
<tr>
<td>C1-1</td><td>C1-2</td><td>D1-1</td><td>D1-2</td>
</tr>
<tr>
<td>C1-3</td><td>C1-4</td><td>D1-3</td><td>D1-4</td>
</tr>
<tr>
<td>C1-5</td><td>C1-6</td><td>D1-5</td><td>D1-6</td>
</tr>
<tr>
<th colspan="2"><a id='E1lnk'>E1</a></th><th colspan="2"><a id='F1lnk'>F1</a></th>
</tr>
<tr>
<td>E1-1</td><td>E1-2</td><td>F1-1</td><td>F1-2</td>
</tr>
<tr>
<td>E1-3</td><td>E1-4</td><td>F1-3</td><td>F1-4</td>
</tr>
<tr>
<td>E1-5</td><td>E1-6</td><td>F1-5</td><td>F1-6</td>
</tr>
</table>
You can see I have essentially got two columns, A1 and B1. The contents are very simple but suffice to say the actual contents will not be that simple.
I want to be able to click B1 and all the cells below it are highlighted, highlights are the easy part, actually selecting the correct cells is much harder.
I will have multiple other small tables adding C1, D1, E1, F1, G1, H1 etc. So there could be a few extra but only ever in columns of two. They will cascade in the rows and so still be part of the parent table but I'm just trying to figure out the best way to go about it, since the table creates them in rows and not columns.
I tried something like you said, however the code gone very long, that's why I have removed some rows.
var a1lnk = document.getElementById('A1lnk');
var a2lnk = document.getElementById('B1lnk');
var a3lnk = document.getElementById('C1lnk');
var a1 = document.getElementById('a1');
var a2 = document.getElementById('a2');
var c1 = document.getElementById('c1');
var c2 = document.getElementById('c2');
function unhighlight () {
b1.removeAttribute('h');
b2.removeAttribute('h');
a1.removeAttribute('h');
a2.removeAttribute('h');
c1.removeAttribute('h');
c2.removeAttribute('h');
}
var b1 = document.getElementById('b1');
var b2 = document.getElementById('b2');
function highlightA () {
unhighlight();
a1.setAttribute('h', true);
a2.setAttribute('h', true);
}
function highlightB () {
unhighlight();
b1.setAttribute('h', true);
b2.setAttribute('h', true);
}
function highlightC () {
unhighlight();
c1.setAttribute('h', true);
c2.setAttribute('h', true);
}
a1lnk.onclick = highlightA;
a2lnk.onclick = highlightB;
a3lnk.onclick = highlightC;
#A1lnk, #B1lnk, #C1lnk {cursor: pointer;}
td[h] {
background-color: orange;
color: #fff;
}
<table border="1">
<tr>
<th colspan="2"><a id='A1lnk'>A1</a></th><th colspan="2"><a id='B1lnk'>B1</a></th>
</tr>
<tr>
<td id="a1">A1-1</td><td id="a2">A1-2</td><td id="b1">B1-1</td><td id="b2">B1-2</td>
</tr>
<tr>
<th colspan="2"><a id='C1lnk'>C1</a></th>
</tr>
<tr>
<td id="c1">C1-1</td><td id="c2">C1-2</td>
</tr>
</table>
Hope, this should work for you.
You should use a class for header instead of different ids. Then on click of header get it's index. Using this index you can easily select the cells below it using nextUntil() method and :nth-child pseudo selector and highlight them like following.
$('.header').click(function() {
var index = $(this).parent().index(),
a = index * 2 + 1,
b = a + 1;
$('.highlight').removeClass('highlight');
var tr = $(this).closest('tr').nextUntil(':has(th)')
tr.find('td:nth-child(' + a + '), td:nth-child(' + b + ')').addClass('highlight');
});
.header {
cursor: pointer;
}
.highlight {
background-color: green;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<table border="1">
<tr>
<th colspan="2"><a class="header">A1</a></th>
<th colspan="2"><a class="header">B1</a></th>
</tr>
<tr>
<td>A1-1</td>
<td>A1-2</td>
<td>B1-1</td>
<td>B1-2</td>
</tr>
<tr>
<td>A1-3</td>
<td>A1-4</td>
<td>B1-3</td>
<td>B1-4</td>
</tr>
<tr>
<td>A1-5</td>
<td>A1-6</td>
<td>B1-5</td>
<td>B1-6</td>
</tr>
<tr>
<th colspan="2"><a class="header">C1</a></th>
<th colspan="2"><a class="header">D1</a></th>
</tr>
<tr>
<td>C1-1</td>
<td>C1-2</td>
<td>D1-1</td>
<td>D1-2</td>
</tr>
<tr>
<td>C1-3</td>
<td>C1-4</td>
<td>D1-3</td>
<td>D1-4</td>
</tr>
<tr>
<td>C1-5</td>
<td>C1-6</td>
<td>D1-5</td>
<td>D1-6</td>
</tr>
<tr>
<th colspan="2"><a class="header">E1</a></th>
<th colspan="2"><a class="header">F1</a></th>
</tr>
<tr>
<td>E1-1</td>
<td>E1-2</td>
<td>F1-1</td>
<td>F1-2</td>
</tr>
<tr>
<td>E1-3</td>
<td>E1-4</td>
<td>F1-3</td>
<td>F1-4</td>
</tr>
<tr>
<td>E1-5</td>
<td>E1-6</td>
<td>F1-5</td>
<td>F1-6</td>
</tr>
</table>