I have a complex HTML table with horizontal and vertical headings looking like this.
The HTML code is simple:
<table>
<tr>
<th>(empty)</th>
<th>Heading 1</th>
<th>Heading 2</th>
</tr>
<tr>
<th>Heading A</th>
<td>content for 1 and A</td>
<td>content for 2 and A</td>
</tr>
<tr>
<th>Heading B</th>
<td>content for 1 and B</td>
<td>content for 2 and B</td>
</tr>
<tr>
<th>Heading C</th>
<td>content for 1 and C</td>
<td>content for 2 and C</td>
</tr>
</table>
Now, I want to give the users of my website the possibility to swap the content of the table. I.e.: change the horizontal and vertical order, so that afterwards the vertical headings are horizontal, the horizontal headings are vertical and the table cells accordingly. Sounds complicated, but you will get it by looking at the picture:
The HTML would be now:
<table>
<tr>
<th>(empty)</th>
<th>Heading A</th>
<th>Heading B</th>
<th>Heading C</th>
</tr>
<tr>
<th>Heading 1</th>
<td>content for 1 and A</td>
<td>content for 1 and B</td>
<td>content for 1 and C</td>
</tr>
<tr>
<th>Heading 2</th>
<td>content for 2 and A</td>
<td>content for 2 and B</td>
<td>content for 2 and C</td>
</tr>
</table>
Technically speaking, evey table cell (th and td) has to swap its indices: The column index should become the row index and the column index should become the rown index. But how do I do that using JavaScript and or JQuery?
I already found out how to get the row and column index:
//column
$(this).parent().children().index(this);
//row
$(this).parent().parent().children().index(this.parentNode);
But there seems no JQuery function for "set table cell position", there is just .inserAfter and honestly, I don't know how to cope with that.
Change your html like this
<table>
<tbody>
<tr>
<td>(empty)</td>
<td>Heading 1</td>
<td>Heading 2</td>
</tr>
<tr>
<td>Heading A</td>
<td>content for 1 and A</td>
<td>content for 2 and A</td>
</tr>
<tr>
<td>Heading B</td>
<td>content for 1 and B</td>
<td>content for 2 and B</td>
</tr>
<tr>
<td>Heading C</td>
<td>content for 1 and C</td>
<td>content for 2 and C</td>
</tr>
</tbody>
</table>
And call this function
function Swap(){
var t= document.getElementsByTagName('tbody')[0],
r= t.getElementsByTagName('tr'),
cols= r.length, rows= r[0].getElementsByTagName('td').length,
cell, next, tem, i= 0, tbod= document.createElement('tbody');
while(i<rows){
cell= 0;
tem= document.createElement('tr');
while(cell<cols){
next= r[cell++].getElementsByTagName('td')[0];
tem.appendChild(next);
}
tbod.appendChild(tem);
++i;
}
t.parentNode.replaceChild(tbod, t);
}
Test it here
function invertTable(table){
var $table = $(table);
var invertedTable = [];
for(var i=0 ; i < $table.find('tr:first th').length ; i++){
invertedTable.push([]);
}
$table.find('th,td').each(function(){
invertedTable[$(this).index()].push($(this).text());
})
var $newTable = $('<table></table>');
for(var i=0 ; i < invertedTable.length ; i++){
var $newTr = $('<tr></tr>');
for(var j = 0 ; j < invertedTable[i].length ; j++){
if(j == 0 || i == 0){
$newTr.append('<th>'+invertedTable[i][j]+'</th>');
}
else{
$newTr.append('<td>'+invertedTable[i][j]+'</td>');
}
}
$newTable.append($newTr);
}
$table.after($newTable)
$table.remove();
}
http://jsfiddle.net/xybmoadx/1/
Maybe you should build two tables and hide the inapropriate each time.
html :
<table>
<tr>
<th>(empty)</th>
<th>Heading 1</th>
<th>Heading 2</th>
</tr>
<tr>
<th>Heading A</th>
<td>content for 1 and A</td>
<td>content for 2 and A</td>
</tr>
<tr>
<th>Heading B</th>
<td>content for 1 and B</td>
<td>content for 2 and B</td>
</tr>
<tr>
<th>Heading C</th>
<td>content for 1 and C</td>
<td>content for 2 and C</td>
</tr>
</table>
<table style="display:none;">
<tr>
<th>(empty)</th>
<th>Heading A</th>
<th>Heading B</th>
<th>Heading C</th>
</tr>
<tr>
<th>Heading 1</th>
<td>content for 1 and A</td>
<td>content for 1 and B</td>
<td>content for 1 and C</td>
</tr>
<tr>
<th>Heading 2</th>
<td>content for 2 and A</td>
<td>content for 2 and B</td>
<td>content for 2 and C</td>
</tr>
</table>
notice the style: "display:none;" on second table.
Now all you need to do is toogle tables when ever you whant. Lets say you want to toggle on table click :
$('table').on('click', function() {
$('table').toggle();
});
This solution assums your table is static. It is a quick and easy workarround though.
Hope that helps
PS: you can test it here
function transpose(arr){
return Object.keys(arr[0]).map(function (c) { return arr.map(function (r) { return r[c]; }); });
}
function transposeTable(table){
var rows = [];
var tableString = '';
table.find('tr').each(function(i,row){
var rowArray = [];
row = $(row);
row.find('th,td').each(function(i,cell){
cell = $(cell);
rowArray.push({
value : cell.text(),
type : cell.is('th') ? 'th':'td'
})
})
rows.push(rowArray);
});
rows = transpose(rows);
rows.forEach(function(row){
tableString += '<tr>';
row.forEach(function(cell){
tableString += '<' + cell.type + '>';
tableString += cell.value;
tableString += '</' + cell.type + '>';
})
tableString += '</tr>';
});
return tableString;
}
JSfiddle
Not clean but it works .
Buid the array dynamically from javascript. That would be the easiest way to make it... as well as maintain it.
Say a multi-dimensional array [x][y] represents all you need.
[0][0] will obviously be null...
Define a table element in HTML give it an ID, say 'table'... In JavaScript then...
Defining it should be easy...
var t = new Array();
t[0] = new Array();
t[0][0] = 'Heading 1';
t[0][1] = 'Heading 2';
and so on..
On jQuery's Document Ready event, you can initialize it...
$(document).ready(function() {
for (var i = 0; i < t.length; i++) {
$('#table').append('<tr id=tr' + 0 + '></tr>');
for (var j = 0; j < t[i].length; j++) {
if (i == 0)
$('#tr' + i).append('<th>' + t[i][j] + '</th>');
else
$('#tr' + i).append('<td>' + t[i][j] + '</td>');
}
}
var t_trans = t[0].map(function(col, i) {
return t.map(function(row) {
return row[i];
});
});
});
I've used code from this answer to transpose the created array and store it in a different variable. Transposing a 2D-array in JavaScript
Now all you need to do on click of the 'Transpose Button' in HTML is delete existing markup inside the 'table', and recreate it using the transposed array.. so during the onclick event,
$('#table').empty();
for (var i = 0; i < t_trans.length; i++) {
$('#table').append('<tr id=tr' + 0 + '></tr>');
for (var j = 0; j < t_trans[i].length; j++) {
if (i == 0)
$('#tr' + i).append('<th>' + t_trans[i][j] + '</th>');
else
$('#tr' + i).append('<td>' + t_trans[i][j] + '</td>');
}
}
This should do it... Hell, you can even make a composite function taking the array as an argument to do the job for you, something like function populate(arr){} and the nested loop inside..
function populate(arr){
for (var i = 0; i < arr.length; i++) {
$('#table').append('<tr id=tr' + 0 + '></tr>');
for (var j = 0; j < arr[i].length; j++) {
if (i == 0)
$('#tr' + i).append('<th>' + arr[i][j] + '</th>');
else
$('#tr' + i).append('<td>' + arr[i][j] + '</td>');
}
}
}
and call populate(t) on ready, populate(t_trans) after emptying it on button-click.
Look at this jsfiddle I created for you:
http://jsfiddle.net/carloscalla/yuhx2tgk/3/
It stores the table cell's values in arrays and builds up a table from them. Then swaps the order when button is clicked.
HTML:
<div id="table-wrapper"></div>
<button id="toggler">Toggler</button>
JS:
var max_rows = 4;
var a = new Array();
for (i = 0; i < max_rows; i++)
a[i] = new Array();
a[0][0] = "(empty)";
a[0][1] = "Heading 1";
a[0][2] = "Heading 2";
a[1][0] = "Heading A";
a[1][1] = "content 1 A";
a[1][2] = "content 2 A";
a[2][0] = "Heading B";
a[2][1] = "content 1 B";
a[2][2] = "content 2 B";
a[3][0] = "Heading C";
a[3][1] = "content 1 C";
a[3][2] = "content 2 C";
var direction = 0;
// 0 for horizontal
// 1 for vertical
function changeDirection(){
if (direction == 1) direction = 0;
else if (direction == 0) direction = 1;
resetTable();
}
function resetTable(){
var $table_body = $('<table><tbody></tbody></table>');
if (direction == 0){
for (x = 0; x < a.length; x++){
$table_body.append('<tr></tr>');
$last_row = $table_body.find('tr').last();
for (v = 0; v < a[x].length; v++){
$last_row.append('<td>' + a[x][v] + '</td>');
}
}
}
else{
for (x = 0; x < a[0].length; x++){
$table_body.append('<tr></tr>');
$last_row = $table_body.find('tr').last();
for (v = 0; v < a.length; v++){
$last_row.append('<td>' + a[v][x] + '</td>');
}
}
}
$('#table-wrapper').html($table_body);
}
$('#toggler').on('click', changeDirection);
Results are these:
Original table:
Updated table:
Related
I've a table in HTML looks like this:
Subjects
n1
n2
n3
subject1
10
0
0
subject2
0
5
20
<table>
<thead>
<tr>
<th class="subject">Subjects</th>
<th>n1</th>
<th>n2</th>
<th>n3</th>
</tr>
</thead>
<tbody>
<tr>
<th class="subject">subject1</th>
<td>10</td>
<td>0</td>
<td>0</td>
</tr>
<tr>
<th class="subject">subject2</th>
<td>0</td>
<td>5</td>
<td>20</td>
</tr>
</tbody>
</table>
Is there any thought or approach with javascript I could re-order columns in a specific order let order = ['n2','n1','n3']:
Subjects
n2
n1
n3
subject1
0
10
0
subject2
5
0
20
I've solved by turning the table into 2-dimensional array and sort it and turn it back into table HTML:
function tableToArray(tbl, opt_cellValueGetter) {
opt_cellValueGetter = opt_cellValueGetter || function(td) {
return td.textContent || td.innerText;
};
var twoD = [];
for (var rowCount = tbl.rows.length, rowIndex = 0; rowIndex < rowCount; rowIndex++) {
twoD.push([]);
}
for (var rowIndex = 0, tr; rowIndex < rowCount; rowIndex++) {
var tr = tbl.rows[rowIndex];
for (var colIndex = 0, colCount = tr.cells.length, offset = 0; colIndex < colCount; colIndex++) {
var td = tr.cells[colIndex],
text = opt_cellValueGetter(td, colIndex, rowIndex, tbl);
while (twoD[rowIndex].hasOwnProperty(colIndex + offset)) {
offset++;
}
for (var i = 0, colSpan = parseInt(td.colSpan, 10) || 1; i < colSpan; i++) {
for (var j = 0, rowSpan = parseInt(td.rowSpan, 10) || 1; j < rowSpan; j++) {
twoD[rowIndex + j][colIndex + offset + i] = text;
}
}
}
}
return twoD;
}
let order = ['n2', 'n1', 'n3', "Subjects"];
const sort2dArrayColumsByFirstRow = (array) => {
if (!Array.isArray(array)) return [];
const sortedFirstRow = array[0]
.map((item, i) => ({
v: item,
i: i
}))
.sort((a, b) => {
return order.indexOf(a.v) - order.indexOf(b.v);
});
return array.map((row) => row.map((_, i) => row[sortedFirstRow[i].i]));
};
function arrayToTable(columnNames, dataArray) {
var myTable = document.createElement('table');
var y = document.createElement('tr');
myTable.appendChild(y);
for (var i = 0; i < columnNames.length; i++) {
var th = document.createElement('th'),
columns = document.createTextNode(columnNames[i]);
th.appendChild(columns);
y.appendChild(th);
}
for (var i = 0; i < dataArray.length; i++) {
var row = dataArray[i];
var y2 = document.createElement('tr');
for (var j = 0; j < row.length; j++) {
myTable.appendChild(y2);
var th2 = document.createElement('td');
var date2 = document.createTextNode(row[j]);
th2.appendChild(date2);
y2.appendChild(th2);
}
}
document.querySelector('#tableEl').innerHTML = myTable.innerHTML;
}
let arr = tableToArray(document.querySelector('#tableEl'))
console.log('before:', arr)
let arrOrdered = sort2dArrayColumsByFirstRow(arr);
console.log('after:', arrOrdered);
arrayToTable(arrOrdered[0], arrOrdered.slice(1))
<table id="tableEl">
<thead>
<tr>
<th class="subject">Subjects</th>
<th>n1</th>
<th>n2</th>
<th>n3</th>
</tr>
</thead>
<tbody>
<tr>
<th class="subject">subject1</th>
<td>10</td>
<td>0</td>
<td>0</td>
</tr>
<tr>
<th class="subject">subject2</th>
<td>0</td>
<td>5</td>
<td>20</td>
</tr>
</tbody>
</table>
This is a good DOM question.
Tables are modified by the TABLE API.
https://html.spec.whatwg.org/multipage/tables.html
The TABLE element has THEAD, TFOOT, and TBODY elements. Use of these elements provides structure for your javascript. (Good job so far).
<table id="s-table">
<thead>
<tr>
<th class="subject">Subjects</th>
<th>n1</th>
<th>n2</th>
<th>n3</th>
</tr>
</thead>
<tbody>
<tr>
<th class="subject">subject1</th>
<td>10</td>
<td>0</td>
<td>0</td>
</tr>
<tr>
<th class="subject">subject2</th>
<td>0</td>
<td>5</td>
<td>20</td>
</tr>
</tbody>
</table>
Next, you'll need some javascript.
You'll also find insertBefore, and possibly before, and after Element methods handy.
https://developer.mozilla.org/en-US/docs/Web/API/Node/insertBefore
Get the TBODY element.
For each row, reorder(cell[i], cell[j]).
Let's start with
function resortTBody(tBody) {
const rows = tBody.rows;
for(let i = 0; i < tBody.rows.length; i++) {
reorderRow(rows[i]);
}
}
function reorderRow(row) {
let cells = row.cells;
row.insertBefore(cells[2], cells[1]);
}
This code has a hard-coded swap of cells. To reorder the cells to match a specific order, you'll need to modify reorderRow:
reorderRow(row, newOrder);
The TH's can be similarly reordered.
Design Notes: It's a good idea to minimize scope of identifiers. That is, put them in scope only as broad as it can be maximally justified.
If reorderRow is only needed for resortTbody, it can be restricted to private access.
let resortTBody = function(tBody) {
function resortTBodyInner(tBody) {
const rows = tBody.rows;
for(let i = 0; i < tBody.rows.length; i++) {
reorderRow(rows[i]);
}
}
function reorderRow(row) {
let cells = row.cells;
row.insertBefore(cells[2], cells[1]);
}
resortTBodyInner(tBody);
resortTBody = resortTBodyInner;
};
It might be desirable to maintain the column headers but resort their contents. That would require a subtle change to the approach.
It might be desirable to reset the table to its original state. All of that can be done.
The following one-liner will reorganize the columns in the desired order:
document.querySelectorAll("#tableEl tr").forEach(tr=>[...tr.children].forEach((_,i,a)=>tr.append(a[[0,2,1,3][i]])));
<table id="tableEl">
<thead>
<tr>
<th class="subject">Subjects</th>
<th>n1</th>
<th>n2</th>
<th>n3</th>
</tr>
</thead>
<tbody>
<tr>
<th class="subject">subject1</th>
<td>10</td>
<td>0</td>
<td>0</td>
</tr>
<tr>
<th class="subject">subject2</th>
<td>0</td>
<td>5</td>
<td>20</td>
</tr>
</tbody>
</table>
I have updated the code, which is currently working up to the Place for each Group #. The existing code determines a matching Section # with Group #, and combines the TR values, adding the Average(s) and dividing by the number of TR(s), and removing any duplicate(s).
I am attempting to determine a Place for each Group # within a Section #. (*example - Section 1: Group #1 - 1st / Group #2 - 3rd / Group #3 - 2nd)
$(function() {
alert('View Table Before Grouping');
sort_array();
combine_rows();
compute_average();
});
//function sort_array
function sort_array() {
var $table = $('table');
var header = $('table tr:first-child').html();
var l = [];
$table.find('tr').each( function() {
//get values from td(s) from each row
var section = $(this).find('.class_section_number').text();
var group_number = $(this).find('.group_number').text();
var average = $(this).find('td:eq(2)').text();
var place = $(this).find('td:eq(3)').text();
//add to array
l.push([section, group_number, average, place]);
//remove saved row
$(this).remove();
});
l = l.slice(1);
//sort the array by section #, then group #
l.sortBy(0, true, 1, true, 2, false);
$table.prepend(header);
//rebuild table after sort
$.each(l, function(key, value) {
$('table').append('<tr><td class="class_section_number">' + value[0] + '</td><td class="group_number">' + value[1] + '</td><td class="grade">' + value[2] + '</td><td>' + value[3] + '</td></tr>');
});
}
//function combine_rows
function combine_rows() {
$('table tr').each( function() {
//get current row data
var section = $(this).find('.class_section_number').text();
var group = $(this).find('.group_number').text();
var average = $(this).find('td:eq(2)').text();
//get next row data
var next_section = $(this).next('tr').find('.class_section_number').text();
var next_group = $(this).next('tr').find('.group_number').text();
var next_average = $(this).next('tr').find('td:eq(2)').text();
//check for section # / group # row match
if ((section === next_section) && (group === next_group)) {
//check for empty average
if (average === 'No Data') {
average = 0 + ',' + next_average;
}
else {
average = average + ',' + next_average;
}
//set combined average
$(this).next('tr').find('td:eq(2)').text(average);
//remove matching row
$(this).remove();
}
});
}
//function compute_average
function compute_average() {
$('table tr').each( function() {
//get average from row
var average = $(this).find('td:eq(2)').text();
//split average into array (*if comma separated values)
average_array = average.split(',');
var total = 0;
//total average values from array / divide by count and set
for (var i = 0; i < average_array.length; i++) {
total += (+average_array[i]);
$(this).find('td:eq(2)').text((total / average_array.length).toFixed(2));
}
});
}
//array sort function
Array.prototype.sortBy = function (propertyName, sortDirection) {
var sortArguments = arguments;
this.sort(function (objA, objB) {
var result = 0;
for (var argIndex = 0; argIndex < sortArguments.length && result === 0; argIndex += 2) {
var propertyName = sortArguments[argIndex];
result = (objA[propertyName] < objB[propertyName]) ? -1 : (objA[propertyName] > objB[propertyName]) ? 1 : 0;
result *= sortArguments[argIndex + 1] ? 1 : -1;
}
return result;
});
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.1/jquery.min.js"></script>
<table>
<tbody>
<tr>
<th>Section #</th>
<th>Group #</th>
<th>Averages</th>
<th>Place</th>
</tr>
<tr>
<td class="class_section_number">1</td>
<td class="group_number">2</td>
<td class="grade">78.29</td>
<td>Test Place 1st</td>
</tr>
<tr>
<td class="class_section_number">1</td>
<td class="group_number">2</td>
<td class="grade">85.52</td>
<td>Test Place 1st</td>
</tr>
<tr>
<td class="class_section_number">1</td>
<td class="group_number">1</td>
<td class="grade">74.41</td>
<td>Test Place 2nd</td>
</tr>
<tr>
<td class="class_section_number">1</td>
<td class="group_number">2</td>
<td>No Data</td>
<td>Test Place 3rd</td>
</tr>
<tr><td class="class_section_number">2</td>
<td class="group_number">1</td>
<td class="grade">78.90</td>
<td>Test Place 2nd</td>
</tr>
</tr>
<tr><td class="class_section_number">1</td>
<td class="group_number">3</td>
<td class="grade">91.03</td>
<td>Test Place 2nd</td>
</tr>
</tr>
<tr><td class="class_section_number">2</td>
<td class="group_number">2</td>
<td class="grade">81.69</td>
<td>Test Place 2nd</td>
</tr>
<tr>
<td class="class_section_number">2</td>
<td class="group_number">2</td>
<td class="grade">81.13</td>
<td>Test Place 1st</td>
</tr>
<tr>
<td class="class_section_number">2</td>
<td class="group_number">2</td>
<td class="grade">78.13</td>
<td>Test Place 1st</td>
</tr>
</tbody>
</table>
How to get the sum and average of the last column.In my code it wont get the correct value if the table has one,two and three rows.This works only on table with 4 rows.I know something wrong with my code but i cant figure it out how the loop works within .each function.
Important note:this runs with keyup event to update table data.Its not just a display.To be exact it is an update form.
Desired output
Item | value1 | value 2 | value3 |value 4 | Average
01 90 88 87 80 82.25
Total average 82.25 result of 82.25/1 number of row
if two rows
Item | value1 | value 2 | value3 |value 4 | Average
01 90 88 87 80 82.25
02 80 85 86 84 83.75
Total average 83 result of (82.25+83.75)/2 number of rows
But the result comes out with multiple loops
Here is the console.log(totalAverage)
86.25
176
264.75
353.5
442.25
86.25
176
264.75
353.5
442.25
Problem:How to suppress or skip this unnecessary values.I only need the 86.25 to display in total-ave.Note: this is only single row right now and have already incountered this miscaculation, how much more if the table has multiple rows then?
Html
<tr>
<th colspan="12"><h4>Card</h4></th>
</tr>
<tr>
<th colspan="3">Subjects</th>
<th colspan="2">First Grading</th>
<th colspan="2">Second Grading</th>
<th colspan="2">Third Grading</th>
<th colspan="2">Fourth Grading</th>
<th>Average</th>
</tr>
</thead>
<tbody>
#foreach($update_card['AllGrade'] as $subject)
{!! Form::hidden('grade_id[]',$subject['grade_id']) !!}
<tr>
<th colspan="3">{!! $subject->subject !!}</th>
<td colspan="2">{!! Form::text('term_1[]',$subject->term_1,['class'=>'form-control','name[]'=>'term_1','id[]'=>'term_1','value'=>'0']) !!}</td>
<td colspan="2">{!! Form::text('term_2[]',$subject->term_2,['class'=>'form-control','name[]'=>'term_2','id[]'=>'term_2','value'=>'0']) !!}</td>
<td colspan="2">{!! Form::text('term_3[]',$subject->term_3,['class'=>'form-control','name[]'=>'term_3','id[]'=>'term_4','value'=>'0']) !!}</td>
<td colspan="2">{!! Form::text('term_4[]',$subject->term_4,['class'=>'form-control','name[]'=>'term_4','id[]'=>'term_4','value'=>'0']) !!}</td>
<td colspan="2" class="average" id="average" value="0"> Average</td>
</tr>
#endforeach
<tr>
<th colspan="11">Total Average:</th>
<th>{!! Form::text('scholar_GPA',$update_card->scholar_GPA,['class'=>'form-control total-ave','name' => 'total-ave','id' => 'total-ave','value' => '0']) !!}</th>
</tr>
Javascript snippet
$("input").on("keyup", function(){
$("tbody tr").each(function() {
var col=1;
var tr =1;
var t = 0;
var a = 0;
$(this).children('td').not(':last').each(function () {
var number = ($(this).children('input').length == 0) ? $(this).html() : $(this).children('input').first().val();
// console.log(number);
// console.log(col);
t += parseInt(number);
// console.log(t);
a = t/col;
col++;
});
$(this).children('td').last().html(a);//last td of the row
// console.log(a);
col=1;
tr++;
});
calcTotalave();
});
// calculate total average
function calcTotalave() {
var totalAve = 0;
var tot=0;
var c = 2;
var count =0;
$( ".average" ).each(function() {
// console.log($(this).html());
var thisAve = parseFloat($(this).html());
if(!thisAve || thisAve === NaN || thisAve == "") {
thisAve = 0;
}
totalAve += thisAve;
//alert('count'+thisAve+totalAve);
console.log(totalAve);
count++;
});
c++;
totalAve = totalAve/c;
// console.log(totalAve);
$("#total-ave").val(totalAve);
}
UPDATED: fiddle below with comments, press space-bar to run, based around the below function.
the fiddle is made to cycle through and calc cells by row, so no .average class required. You will need to adapt it for your html table layouts as per your database output.
calcTotalave();
});
// calculate total average
function calcTotalave() {
var totalAve = 0;
$( ".average" ).each(function() {
var thisAve = parseFloat($(this).text()) || 0; // always return a number
totalAve += thisAve;
});
var Aver = totalAve / $('.average').length;
console.log(totalAve);
$("#total-ave").text(Aver);
}
instead of classing each cell as .average you could use the selector to target all the cells td of a given row:
$('input').change(function() {
calTotalAverages(); // the magic and collect the total average
});
function calTotalAverages(){
var SumAve = 0, nums = 0; // to collect total averages and the number of rows
$('tr').each(function(i) {
if (i > 0) { // ignore the first row
var $this = $(this);
SumAve += calcRowAve($this); // add up the returned averages and run the function
nums ++; // count the rows
}
}); // cycle through each row
var sum = (SumAve / nums);
$('#total-ave').text(sum.toFixed(2)); // display the total average
return sum; // return the total average
}
// calculate total average
function calcRowAve(targetRow) {
var totalAve = 0,
targetCells = targetRow.children(),
targLength = targetCells.length - 2; // total number of values in a row
targetCells.each(function(i) {
if (i > 0 && i <= targLength) {
var thisAve = parseFloat($('input',this).val()) || parseFloat($(this).text()) || 0; // always return a number
totalAve += thisAve;
} // check to ignore the first cell and the last
});
var Aver = totalAve / targLength; // get the average
targetCells.last().text(Aver); // update the last cell of the row
return Aver; // return the average for this row
}
#total-ave {
position: fixed;
right: 2em;
top: 8em;
}
input{
width: 5em;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<table>
<tr>
<th>item 1</th>
<th>value 1</th>
<th>value 2</th>
<th>value 3</th>
<th>value 4</th>
<th>average</th>
</tr>
<tr>
<td>1</td>
<td>90</td>
<td>88</td>
<td>87</td>
<td>80</td>
<td></td>
</tr>
<tr>
<td>2</td>
<td>80</td>
<td>85</td>
<td>86</td>
<td>84</td>
<td></td>
</tr>
<tr>
<td>3</td>
<td><input type='number'></td>
<td><input type='number'></td>
<td><input type='number'></td>
<td><input type='number'></td>
<td></td>
</tr>
</table>
<div id='total-ave'></div>
I have a simple HTML table, which uses rowspans in some random columns. An example might look like
A | B |
---|---| C
D | |
---| E |---
F | | G
I'd like to iterate over the rows such that I see rows as A,B,C, D,E,C, then F,E,G.
I think I can probably cobble together something very convoluted using cell.index() to check for "missed" columns in later rows, but I'd like something a little more elegant...
without jquery:
function tableToMatrix(table) {
var M = [];
for (var i = 0; i < table.rows.length; i++) {
var tr = table.rows[i];
M[i] = [];
for (var j = 0, k = 0; j < M[0].length || k < tr.cells.length;) {
var c = (M[i-1]||[])[j];
// first check if there's a continuing cell above with rowSpan
if (c && c.parentNode.rowIndex + c.rowSpan > i) {
M[i].push(...Array.from({length: c.colSpan}, () => c))
j += c.colSpan;
} else if (tr.cells[k]) {
var td = tr.cells[k++];
M[i].push(...Array.from({length: td.colSpan}, () => td));
j += td.colSpan;
}
}
}
return M;
}
var M = tableToMatrix(document.querySelector('table'));
console.table(M.map(r => r.map(c => c.innerText)));
var pre = document.createElement('pre');
pre.innerText = M.map(row => row.map(c => c.innerText).join('\t')).join('\n');
document.body.append(pre);
td {
border: 1px solid rgba(0,0,0,.3);
}
<table>
<tr>
<td colspan=2>A</td>
<td rowspan=2>B</td>
</tr>
<tr>
<td>C</td>
<td rowspan=3>D</td>
</tr>
<tr>
<td rowspan=2>E</td>
<td rowspan=4>F</td>
</tr>
<tr></tr>
<tr>
<td rowspan=2 colspan=2>G</td>
</tr>
<tr></tr>
<tr>
<td rowspan=3 colspan=3>H</td>
</tr>
<tr></tr>
<tr></tr>
<tr>
<td colspan=3>I</td>
</tr>
</table>
Try this:
<table id="tbl">
<tr>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td colspan="2" rowspan="2">A</td>
<td rowspan="2">C</td>
</tr>
<tr>
<td rowspan="2">E</td>
</tr>
<tr>
<td>F</td>
<td>G</td>
</tr>
</table>
Script:
var finalResult = '';
var totalTds = $('#tbl TR')[0].length;
var trArray = [];
var trArrayValue = [];
var trIndex = 1;
$('#tbl TR').each(function(){
var currentTr = $(this);
var tdIndex = 1;
trArray[trIndex] = [];
trArrayValue[trIndex] = [];
var tdActuallyTraversed = 0;
var colspanCount = 1;
$('#tbl TR').first().children().each(function(){
if(trIndex > 1 && trArray[trIndex - 1][tdIndex] > 1)
{
trArray[trIndex][tdIndex] = trArray[trIndex - 1][tdIndex] - 1;
trArrayValue[trIndex][tdIndex] = trArrayValue[trIndex - 1][tdIndex];
finalResult = finalResult + trArrayValue[trIndex][tdIndex];
}
else
{
if(colspanCount <= 1)
{
colspanCount = currentTr.children().eq(tdActuallyTraversed).attr('colspan') != undefined ? currentTr.children().eq(tdActuallyTraversed).attr('colspan') : 1;
}
if(colspanCount > 1 && tdIndex > 1)
{
trArray[trIndex][tdIndex] = currentTr.children().eq(tdActuallyTraversed + colspanCount).attr('rowspan') != undefined ?currentTr.children().eq(tdActuallyTraversed + colspanCount).attr('rowspan') : 1;
trArrayValue[trIndex][tdIndex] = trArrayValue[trIndex][tdIndex - 1];
colspanCount--;
}
else
{
trArray[trIndex][tdIndex] = currentTr.children().eq(tdActuallyTraversed).attr('rowspan') != undefined ?currentTr.children().eq(tdActuallyTraversed).attr('rowspan') : 1;
trArrayValue[trIndex][tdIndex] = currentTr.children().eq(tdActuallyTraversed).html();
tdActuallyTraversed++;
}
finalResult = finalResult + trArrayValue[trIndex][tdIndex];
}
tdIndex++;
});
trIndex++;
});
alert(finalResult);
Fiddle
i am not sure about the performance, but it works well.
what I understood with your question is: You want to split the merged cell with same value and then iterate the table simply by row.
I've created a JSFiddle that will split the merged cells with the same value. Then you'll have a table that can be iterated simply by rows to get the desired output that you specified.
See it running here http://jsfiddle.net/9PZQj/3/
Here's the complete code:
<table id="tbl" border = "1">
<tr>
<td>A</td>
<td>B</td>
<td rowspan="2">C</td>
</tr>
<tr>
<td>D</td>
<td rowspan="2">E</td>
</tr>
<tr>
<td>F</td>
<td>G</td>
</tr>
</table>
<br>
<div id="test"> </div>
Here's the jquery that is used to manipulate the table's data.
var tempTable = $('#tbl').clone(true);
var tableBody = $(tempTable).children();
$(tableBody).children().each(function(index , item){
var currentRow = item;
$(currentRow).children().each(function(index1, item1){
if($(item1).attr("rowspan"))
{
// copy the cell
var item2 = $(item1).clone(true);
// Remove rowspan
$(item1).removeAttr("rowspan");
$(item2).removeAttr("rowspan");
// last item's index in next row
var indexOfLastElement = $(currentRow).next().last().index();
if(indexOfLastElement <= index1)
{
$(currentRow).next().append(item2)
}
else
{
// intermediate cell insertion at right position
$(item2).insertBefore($(currentRow).next().children().eq(index1))
}
}
});
console.log(currentRow)
});
$('#test').append(tempTable);
You can use this Gist. It supports all the requirements by W3C, even "rowspan=0" (which seems to be only supported by Firefox).
I have a table on my page html :
<table id="myTab">
<tr>
<td class="reference">1</td>
<td>item1</td>
<td>Info item - 1</td>
</tr>
<tr>
<td class="reference">2</td>
<td>item2</td>
<td>Info item - 2</td>
</tr>
<tr>
<td class="reference">3</td>
<td>item3</td>
<td>Info item - 3</td>
</tr>
<tr>
<td class="reference">4</td>
<td>item4</td>
<td>Info item - 4</td>
</tr>
<table>
How I can select an element of my table with class reference innerHtml value=3?
var el = $(myTab).find('???')
Assuming you want a reference to just that td element, and the markup you've shown is the extent of your current structure, you can use the :contains selector:
var elem = $(".reference:contains('3')");
If you have other td elements containing the character 3 (e.g. 13) they will also be matched. In that case, it's probably better to use the .filter() method:
var elem = $(".reference").filter(function () {
return $(this).text() === "3";
});
var el = $("#myTab").find("td.reference:contains(3)")
...will work assuming the :contains selector is good enough - noting that it matches on (in this case) "3" anywhere within the content of the element.
If you need an exact match you can use .filter():
var el = $("#myTab").find("td.reference")
.filter(function() { return $(this).html() === "3"; });
Or a pure javascript:
function func() {
var myTab= document.getElementById('myTab');
var len = myTab.rows.length;
for (var r = 0; r < len; r++) {
var rowLen = myTab.rows[r].cells.length;
for (var cel = 0; cel < rowLen; cel++) {
if(myTab.rows[r].cells[c].innerHTML === "3" && myTab.rows[r].cells[c].className === "reference")
{
alert("found:"+myTab.rows[r].cells[c].innerHTML);
}
}
}
}