Sort table data with expandable parts - javascript

Right now I have this:
<style>
table {
border-spacing: 0;
width: 100%;
border: 1px solid #ddd;
}
th, td {
text-align: left;
padding: 16px;
}
tr:nth-child(even) {
background-color: #f2f2f2
}
</style>
<table id="schInfoTable">
<thead>
<th onclick="sortTable(0)">Date</th>
<th>Amount</th>
<th>Count</th>
</thead>
<tbody>
<tr>
<td><a onclick="openView('2018-11-14')">2018-11-14</a></td>
<td>$23,000.00</td>
<td>12</td>
</tr>
<tr style="display:none; background-color: #cbe7cb;" class="2018-11-14">
<td>Mandy</td>
<td>Designer</td>
<td>View</td>
</tr>
<tr style="display:none; background-color: #cbe7cb;" class="2018-11-14">
<td>Robert</td>
<td>Cook</td>
<td>View</td>
</tr>
<tr>
<td><a onclick="openView('2018-11-13')">2018-11-13</a></td>
<td>$13,000.00</td>
<td>8</td>
</tr>
<tr style="display:none; background-color: #cbe7cb;" class="2018-11-13 branches">
<td>James</td>
<td>Driver</td>
<td>View</td>
</tr>
</tbody>
</table>
<script>
function openView(showID){
$("."+showID).toggle();
}
function sortTable(n) {
var table, rows, switching, i, x, y, shouldSwitch, dir, switchcount = 0;
table = document.getElementById("schInfoTable");
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;
}
}
}
}
</script>
jsfiddle code here: jsfiddle
When you click on dates it will expand and show additional table row with its data:
But when you click on Date header in order sort by date it all goes wrong:
As you can see, green highlighted table data goes all together to the bottom but they should be like this:
How do I achieve this?
UPDATE: Thanks to David784's code I was able to get desired result, had to do a little tweek though but all credit to David. Here is what I have now:
function openView(showID) {
$("." + showID).toggle();
}
function sortTable(n) {
var table, rows, i, x, y = 0;
var compare1, compare2;
table = document.getElementById("schInfoTable");
switching = true;
rows = table.querySelectorAll('tr.sort');
var sortArr = [];
for (i = 0; i < rows.length; i++) {
x = rows[i];
if (i + 1 in rows) y = rows[i + 1].previousElementSibling;
else y = x.parentElement.lastChild;
var obj = {
sort: x.getElementsByTagName("TD")[n].textContent.toLowerCase(),
range: document.createRange()
};
obj.range.setStartBefore(x);
obj.range.setEndAfter(y);
sortArr.push(obj);
}
function fnSortArrAsc(a, b) {
if (a.sort > b.sort) return 1;
else if (a.sort < b.sort) return -1;
else return 0;
}
function fnSortArrDesc(a, b) {
if (a.sort < b.sort) return 1;
else if (a.sort > b.sort) return -1;
else return 0;
}
compare1 = rows[0].getElementsByTagName("TD")[0].textContent.toLowerCase();
compare2 = rows[rows.length-1].getElementsByTagName("TD")[0].textContent.toLowerCase();
if(compare1 < compare2){
sortArr = sortArr.sort(fnSortArrDesc);
}else{
sortArr = sortArr.sort(fnSortArrAsc);
}
frag = document.createDocumentFragment();
for (i = 0; i < sortArr.length; i++) {
x = sortArr[i];
frag.appendChild(x.range.extractContents());
}
table.appendChild(frag);
}
Full working code is here: jsfiddle

This would be one way of accomplishing what you're trying to do, assuming you want to keep your table structure pretty much the same.
Brief description of the javascript:
assumptions:
add class on each "top level" TR. These are the rows that get sorted
any non-top-level TR is assumed to be a sub-row of the top-level TR above it, and will be moved with that row.
methodology
use that new class added to top-level TRs in a querySelectorAll to get a list of everything we want to sort.
loop: create an array of objects, consisting of
sort value (lower case string contents of correct TD)
DOM range of row and all sub-rows under it
then use built-in javascript Array.sort with simple custom sorting function.
loop: extract all range contents in order into a documentFragment
append documentFragment back to the table
The reason why I'm using the documentFragment is that it saves on DOM reflow and rendering, as described here on MDN, compared with appending each range directly back to the table element one at a time.
Note: if you have a table footer, you would probably want to make use of the tbody element instead of just dealing directly with the table.
function openView(showID) {
$("." + showID).toggle();
}
function sortTable(n) {
var table, rows, switching, i, x, y, shouldSwitch, dir, switchcount = 0;
table = document.getElementById("schInfoTable");
switching = true;
//Set the sorting direction
dir = 1;
var thEl = table.querySelectorAll('th')[n];
if (thEl.classList.contains('asc')) dir = -1;
thEl.classList.toggle('asc');
rows = table.querySelectorAll('tr.sort');
var sortArr = [];
for (i = 0; i < rows.length; i++) {
x = rows[i];
if (i + 1 in rows) y = rows[i + 1].previousElementSibling;
else y = x.parentElement.lastChild;
var obj = {
sort: x.getElementsByTagName("TD")[n].textContent.toLowerCase(),
range: document.createRange()
};
obj.range.setStartBefore(x);
obj.range.setEndAfter(y);
sortArr.push(obj);
}
function fnSortArr(a, b) {
if (a.sort > b.sort) return 1 * dir;
else if (a.sort < b.sort) return -1 * dir;
else return 0;
}
sortArr = sortArr.sort(fnSortArr);
console.log(JSON.stringify(sortArr, null, 2));
frag = document.createDocumentFragment();
for (i = 0; i < sortArr.length; i++) {
x = sortArr[i];
frag.appendChild(x.range.extractContents());
}
table.appendChild(frag);
}
table {
border-spacing: 0;
width: 100%;
border: 1px solid #ddd;
}
th,
td {
text-align: left;
padding: 16px;
}
tr:nth-child(even) {
background-color: #f2f2f2
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<table id="schInfoTable">
<thead>
<th onclick="sortTable(0)">Date</th>
<th>Amount</th>
<th>Count</th>
</thead>
<tbody>
<tr class='sort'>
<td><a onclick="openView('2018-11-14')">2018-11-14</a></td>
<td>$23,000.00</td>
<td>12</td>
</tr>
<tr style="display:none; background-color: #cbe7cb;" class="2018-11-14">
<td>Mandy</td>
<td>Designer</td>
<td>View</td>
</tr>
<tr style="display:none; background-color: #cbe7cb;" class="2018-11-14">
<td>Robert</td>
<td>Cook</td>
<td>View</td>
</tr>
<tr class='sort'>
<td><a onclick="openView('2018-11-13')">2018-11-13</a></td>
<td>$13,000.00</td>
<td>8</td>
</tr>
<tr style="display:none; background-color: #cbe7cb;" class="2018-11-13 branches">
<td>James</td>
<td>Driver</td>
<td>View</td>
</tr>
</tbody>
</table>
*EDIT: add sort order toggle
An alternative method commonly used would be to put your sub-rows into a child table. For example, your child data would look something like this:
<tr><td colspan='3'> <table>...</table> </td></tr>.
Then every top-level row has a single child-table row beneath it, and you always sort/move the rows in pairs, instead of dealing with any number of child-rows from zero to infinity.

I was very intrueged by your question and I programmed out the idea I had for it.
I hope this aproach will help you.
let testData = [{
date: "2018-11-14",
amount: 23000,
count: 12,
people: [{
name: "Mandy",
designation: "Designer",
detail: "View",
},
{
name: "Robert",
designation: "Cook",
detail: "View",
}
]
},
{
date: "2018-11-13",
amount: 13000,
count: 8,
people: [{
name: "James",
designation: "Driver",
detail: "View",
}]
}
]
let testDiv = document.querySelector('#test');
let main = function() {
let mainTable = document.createElement('table');
mainTable.classList.add('table-style');
let mainTbody = document.createElement('tbody');
let mainTheader = document.createElement('tr');
mainTheader.onclick = sort;
let dateHeader = document.createElement('th');
dateHeader.textContent = 'Date';
mainTheader.appendChild(dateHeader);
let amountHeader = document.createElement('th');
mainTheader.appendChild(amountHeader);
amountHeader.textContent = 'Amount';
let countHeader = document.createElement('th');
countHeader.textContent = 'Count';
mainTheader.appendChild(countHeader);
mainTable.appendChild(mainTheader);
let counter = 0;
testData.forEach(object => {
console.log('MAIN', object);
counter++;
let rowEl = document.createElement('tr');
if (counter % 2 === 0) {
rowEl.classList.add('uneven-row');
}
rowEl.onclick = toggle;
rowEl.id = object.date;
let keys = Object.keys(object);
keys.forEach(key => {
console.log(key, ": ", object[key]);
if (key !== 'people') {
let colEl = document.createElement('td');
colEl.textContent = object[key];
rowEl.appendChild(colEl);
} else {
mainTbody.appendChild(rowEl);
let subTableRow = document.createElement('tr');
subTableRow.id = 'detail-' + object.date;
subTableRow.classList.add('hidden');
let subTable = document.createElement('table');
subTable.classList.add('sub-table-style');
subTableRow.appendChild(subTable);
let subTbody = document.createElement('tbody');
object[key].forEach(detail => {
console.log('DETAIL', detail);
let subRowEl = document.createElement('tr');
let detailKeys = Object.keys(detail);
detailKeys.forEach(detailKey => {
console.log(detailKey, ": ", detail[detailKey]);
let subColEl = document.createElement('td');
subColEl.textContent = detail[detailKey];
subRowEl.appendChild(subColEl);
});
subTbody.appendChild(subRowEl);
});
subTable.appendChild(subTbody);
mainTbody.appendChild(subTableRow);
}
});
});
mainTable.appendChild(mainTbody);
testDiv.appendChild(mainTable);
}
let toggle = function() {
let detailEl = document.querySelector('#detail-' + this.id);
console.log('TOGGLE', detailEl);
if (detailEl.classList.contains('hidden')) {
detailEl.classList.toggle('hidden');
console.log('SHOW', detailEl.id);
} else {
detailEl.classList.toggle('hidden');
console.log('HIDE', detailEl);
}
}
let compareAsc = function(a, b) {
console.log('A', a);
console.log('B', b);
if (a.date > b.date) {
return 1;
}
if (a.date < b.date) {
return -1;
}
if (a.date === b.date) {
return 0;
}
}
let compareDesc = function(a, b) {
console.log('A', a);
console.log('B', b);
if (a.date < b.date) {
return 1;
}
if (a.date > b.date) {
return -1;
}
if (a.date === b.date) {
return 0;
}
}
let redraw = function() {
while (testDiv.firstChild) {
testDiv.removeChild(testDiv.firstChild);
}
main();
}
let sort = function() {
console.log('SORT', this.classList)
if (!window.sorted) {
console.log('SORT ASC', this);
testData.sort(compareAsc);
window.sorted = true;
redraw();
} else {
console.log('SORT DESC', this);
testData.sort(compareDesc);
window.sorted = false;
redraw();
}
}
main();
body {
font-family: 'Courier New', Courier, monospace;
}
table {
border: 1px solid #ccc;
border-collapse: collapse;
table-layout: fixed;
width: 600px;
}
.sub-table-style {
border: 0px;
background: lawngreen;
}
td {
width: 150px;
padding: 10px;
margin: 0;
border: 0px;
}
.uneven-row {
background: #ddd;
}
.hidden {
visibility: hidden;
display: none;
height: 0px;
overflow: hidden;
}
<div id="test">
</div>

Related

How to set cell size by each column wise using jspdf?

I have so many HTML tables in my application, each table has different no of columns. I wrote the same script code for all pages so I need to set cell size on the basis of table id. I can get table Id how any but cant looping column wise..
reached till here :
$.each(table1, function (i, row) {
rowCount++;
$.each(row, function (j, cellContent) {
if (rowCount == 1) {
doc.margins = 1;
doc.setFont("helvetica");
doc.setFontType("bold");
doc.setFontSize(11);
//trying by this but cant get each cell wise.
for (var k = 0; k <= row[j].length; k++) {
if (TblId == 'Tbl_AddUser' && k==0) {
cellWidth = 20;
}
else if (TblId == 'Tbl_AddUser' && k == 3) {
cellWidth = 40;
}
doc.cell(leftMargin, topMargin, cellWidth, headerRowHeight, cellContent, i);
}
}
})
})
just write the below code inside of first each loop.
if (table.replace('#', '') == 'sample_2') {
for (var k = 0; k < ActualTbl.rows[rowCount].cells.length; k++) {
cellWidth = 30;
}
}
Welcome to StackOverflow.
The correct way to add table-specific settings would be to use css classes.
In your case, you can have css rules based on table IDs.
table#table1 tr td {
width: 200px;
border: 1px solid black;
}
table#table2 tr td {
width: 100px;
border: 1px solid black;
}
<table id='table1'>
<tr>
<td>Cell in table-1</td>
</tr>
</table>
<table id='table2'>
<tr>
<td>Cell in table-2</td>
</tr>
</table>

Append table array value to another array

I have this code below that popups the cell value whenever the user clicks a specific cell.
What I'm currently trying to do is when the user clicks a cell i want that cell value to be appended to another column. I tried using the push method but it doesn't seem to be working. I'm not sure if I'm doing it the wrong way
JFiddle
HTML:
<table id="fruitsTable" class="fruitstableroni skillsTable class">
<thead></thead>
<tbody></tbody>
</table>
JavaScript:
var tbl = document.getElementById("fruitsTable");
if (tbl != null) {
for (var i = 0; i < tbl.rows.length; i++) {
for (var j = 0; j < tbl.rows[i].cells.length; j++)
tbl.rows[i].cells[j].onclick = function () {
obj[key2].push(this); //Trying to push it to the second column.
console.log(this);
};
}
}
function getval(cel) {
//console.log(cel.innerHTML);
}
var obj = {};
var key = "Red Fruits";
obj[key] = ['Apple', 'Cherry', 'Strawberry'];
var myArray = [];
myArray.push(obj);
var key2 = "Green Fruits";
obj[key2] = ['Watermelon', 'Durian', 'Avacado'];
var myArray2 = [];
myArray2.push(obj);
var key3 = "Random Fruits";
obj[key3] = ['Soursop', 'Papaya', 'Pineapple', 'Melon'];
var myArray3 = [];
myArray3.push(obj);
var $header = $("<tr>"),
cols = 0,
bodyString = "";
$.each(obj, function(key, values) {
cols = Math.max(cols, values.length); // find the longest
$header.append($('<th/>').text(key + ": " + values.length));
});
for (var i = 0; i < cols; i++) { // or use .map, but this is more undertandable for beginners
bodyString += '<tr>';
$.each(obj, function(key, values) {
bodyString += '<td>' +
(values[i] ? values[i] : "") + // ternary - instead of using if/else
'</td>';
});
bodyString += '</tr>';
}
$('.fruitsTableClass thead').html($header);
$('.fruitsTableClass tbody').html(bodyString);
var tbl = document.getElementById("fruitsTable");
if (tbl != null) {
for (var i = 0; i < tbl.rows.length; i++) {
for (var j = 0; j < tbl.rows[i].cells.length; j++)
tbl.rows[i].cells[j].onclick = function() {
getval(this);
obj[key2].push(this);
};
}
}
function getval(cel) {
alert(cel.innerHTML);
}
.class {
font-family: Open Sans;
}
.center {
display: flex;
justify-content: center
}
.skillsTable th {
border-left: 1px solid #AAA5A4;
border-right: 1px solid #AAA5A4;
}
table {
float: left;
border-collapse: collapse;
width: 70%
}
td {
border-left: 1px solid #AAA5A4;
border-right: 1px solid #AAA5A4;
padding-top: 8px;
padding-left: 11px;
font-size: 15px;
}
th {
color: #0080ff;
font-weight: normal;
border-bottom: 1px solid #AAA5A4;
padding-bottom: 5px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="center">
<table id="fruitsTable" class="fruitsTableClass skillsTable class">
<thead></thead>
<tbody></tbody>
</table>
</div>
Restructure your code to have a method to redraw UI and to enable event listeners:
function redraw (obj) {
var $header = $('<tr>'),
cols = 0,
bodyString = ''
$.each(obj, function (key, values) {
cols = Math.max(cols, values.length) // find the longest
$header.append($('<th/>').text(key + ': ' + values.length))
})
for (var i = 0; i < cols; i++) { // or use .map, but this is more undertandable for beginners
bodyString += '<tr>'
$.each(obj, function (key, values) {
bodyString += '<td>' +
(values[i] ? values[i] : '') + // ternary - instead of using if/else
'</td>'
})
bodyString += '</tr>'
}
$('.fruitsTableClass thead').html($header)
$('.fruitsTableClass tbody').html(bodyString)
}
function listener (obj) {
tbl = document.getElementById('fruitsTable')
if (tbl != null) {
for (var i = 0; i < tbl.rows.length; i++) {
for (var j = 0; j < tbl.rows[i].cells.length; j++)
tbl.rows[i].cells[j].onclick = function () {
getval(this)
obj[key2].push(this.innerHTML)
redraw(obj)
listener(obj)
};
}
}
}
function getval (cel) {
alert(cel.innerHTML)
}
redraw(obj)
listener(obj)
JSFiddle - https://jsfiddle.net/gnm8wv5f/
To add rows or cells to a table, you should use the methods insertRow() and insertCell().
Example, if you want to add a cell at the beginning of a row (from w3schools):
var row = document.getElementById("myRow");
var x = row.insertCell(0);
x.innerHTML = "New cell";
Or, to insert at the end:
var x = row.insertCell(row.cells.length);
Using cells.length you can find the number of cells in a particluar row, in that way you could know where to insert the new cell.
More info in: w3 | MDN
Try this code.
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>
var daraArray=[];
$(document).ready(function(){
$(".classTd").click(function(){
//console.log($(this).html());
daraArray.push($(this).html());
console.log(daraArray);
});
});
</script>
<style type="text/css">
.classTd{
text-align: center;
}
</style>
</head>
<table id="fruitsTable" class="fruitstableroni skillsTable class" border="1">
<thead></thead>
<tbody>
<tr>
<td class="classTd" width="10%">1</td>
<td class="classTd" width="10%">2</td>
<td class="classTd" width="10%">3</td>
</tr>
<tr>
<td class="classTd" width="10%">4</td>
<td class="classTd" width="10%">5</td>
<td class="classTd" width="10%">6</td>
</tr>
<tr>
<td class="classTd" width="10%">7</td>
<td class="classTd" width="10%">8</td>
<td class="classTd" width="10%">9</td>
</tr>
</tbody>
</table>
</html>
The other answers here are good but you should definitely try AngularJs. The ng-repeat tag will easily cater your functionality.

JavaScript Sorting a List with restrictions

I am making an app in Electron and have a customer list with their name and location. I have a button that allows the user to add a customer, but when doing so the table shows the index. I have another button that sorts out the names in alphabetical order, but if there are rows that have the index in them, it displays those first...
What I would like is some restriction that puts the rows with (0)(1) at the end of the list when sorted rather than the beginning.
Example:
The customers are sorted correctly, but all the rows with 0 come before the rows with actual words when I would like to have the words before the 0's.
Code:
for some reason in this code snippet it actually doesn't show the 0 or 1 index, but it still sorts the rows with nothing before the rows with text...
const back = document.getElementById('back');
const cust = document.getElementById('cust');
const custDiv = document.getElementById('custDiv');
const addCust = document.getElementById('addCust');
const inv = document.getElementById('inv');
const invDiv = document.getElementById('invDiv');
const addItem = document.getElementById('addItem');
// add customer
function appendRowCust() {
var custList = document.getElementById('custList'), // table reference
row = custList.insertRow(custList.rows.length), // append table row
i;
// insert table cells to the new row
for (i = 0; i < custList.rows[0].cells.length; i++) {
createCell(row.insertCell(i), i, 'row');
}
}
// create DIV element and append to the table cell
function createCell(cell, text, style) {
var div = document.createElement('div'), // create DIV element
txt = document.createTextNode(''); // create text node
div.appendChild(txt); // append text node to the DIV
div.setAttribute('class', style); // set DIV class attribute
div.setAttribute('className', style); // set DIV class attribute for IE (?!)
cell.appendChild(div); // append DIV to the table cell
}
// sort customers
function sortCustTable() {
var custList, rows, switching, i, x, y, shouldSwitch;
custList = document.getElementById("custList");
switching = true;
/* Make a loop that will continue until
no switching has been done: */
while (switching) {
// Start by saying: no switching is done:
switching = false;
rows = custList.getElementsByTagName("TR");
/* 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")[0];
y = rows[i + 1].getElementsByTagName("TD")[0];
// Check if the two rows should switch place:
if (x.innerHTML.toLowerCase() > y.innerHTML.toLowerCase()) {
// I 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;
}
}
}
table {
background-color: black;
color: white;
}
tr:nth-child(even) {
background-color: #656565;
}
tr:nth-child(odd) {
background-color: #505050;
}
td {
width: 300px;
max-width: 300px;
height: 30px;
text-align: center;
}
<div id="custDiv">
<div class="addBtns">
<button id="addCust" onclick="appendRowCust()">add customer</button>
</div>
<div style="width: 355px; margin: 0 auto; height: 50px;">
<button id="sortCust" onclick="sortCustTable()">sort</button>
</div>
<div class="custScroll">
<table id="custListTop">
<tr>
<td contenteditable="false">Customers</td>
<td contenteditable="false">Main Location</td>
</tr>
</table>
<table id="custList" contenteditable="true">
<tr>
<td>Someone</td>
<td>something</td>
</tr>
</table>
</div>
</div>
First, you need to summarize what your specifications are. To my understanding, they are this:
Your table needs to be sorted by the first column lexicographically...
Except for numeric or empty strings, which need to be ordered last in the column.
Now, the given answer by #IrkenInvader is correct in pointing out that you don't need to write your own sorting algorithm, but as far as implementing a correct and efficient solution, consider using the built-in algorithm for Array#sort() with some modifications:
function sortCustTable() {
var custList = document.getElementById('custList');
var rows = custList.getElementsByTagName('tr');
var parent = rows[0].parentElement;
var length = rows.length;
var data = [], ref, charCodes;
for (var index = 0; index < length; index++) {
ref = {
row: rows[index],
value: rows[index].firstElementChild.textContent.toUpperCase()
};
if (ref.value === '') {
ref.value = 'k'; // will sort after everything else, including numbers
} else if (!isNaN(ref.value)) {
charCodes = ref.value.split('').map(function (char) {
return Number(char) + 97; // charCode for 'a'
});
// for example, '05' would become 'af'
ref.value = String.fromCharCode.apply(String, charCodes);
}
data.push(ref);
}
data.sort(function (a, b) {
if (a.value > b.value) return 1;
if (a.value < b.value) return -1;
return 0;
});
for (var index = 0; index < length; index++) {
parent.appendChild(data[index].row);
}
}
I opted to use only ECMAScript 5 features, since a comment in your code indicates wanting to support Internet Explorer. I notice you're using const though, so feel free to modify using ES6 if you feel that would be easier.
Putting this together with the rest of your code, you can see it working below. I added some more default values to the table to give you an idea of how well it works:
const back = document.getElementById('back');
const cust = document.getElementById('cust');
const custDiv = document.getElementById('custDiv');
const addCust = document.getElementById('addCust');
const inv = document.getElementById('inv');
const invDiv = document.getElementById('invDiv');
const addItem = document.getElementById('addItem');
// add customer
function appendRowCust() {
var custList = document.getElementById('custList'), // table reference
row = custList.insertRow(custList.rows.length), // append table row
i;
// insert table cells to the new row
for (i = 0; i < custList.rows[0].cells.length; i++) {
createCell(row.insertCell(i), i, 'row');
}
}
// create DIV element and append to the table cell
function createCell(cell, text, style) {
var div = document.createElement('div'), // create DIV element
txt = document.createTextNode(''); // create text node
div.appendChild(txt); // append text node to the DIV
div.setAttribute('class', style); // set DIV class attribute
div.setAttribute('className', style); // set DIV class attribute for IE (?!)
cell.appendChild(div); // append DIV to the table cell
}
// sort customers
function sortCustTable() {
var custList = document.getElementById('custList');
var rows = custList.getElementsByTagName('tr');
var parent = rows[0].parentElement;
var length = rows.length;
var data = [], ref, charCodes;
for (var index = 0; index < length; index++) {
ref = {
row: rows[index],
value: rows[index].firstElementChild.textContent.toUpperCase()
};
if (ref.value === '') {
ref.value = 'k'; // will sort after everything else, including numbers
} else if (!isNaN(ref.value)) {
charCodes = ref.value.split('').map(function (char) {
return Number(char) + 97; // charCode for 'a'
});
// for example, '05' would become 'af'
ref.value = String.fromCharCode.apply(String, charCodes);
}
data.push(ref);
}
data.sort(function (a, b) {
if (a.value > b.value) return 1;
if (a.value < b.value) return -1;
return 0;
});
for (var index = 0; index < length; index++) {
parent.appendChild(data[index].row);
}
}
table {
background-color: black;
color: white;
}
tr:nth-child(even) {
background-color: #656565;
}
tr:nth-child(odd) {
background-color: #505050;
}
td {
width: 300px;
max-width: 300px;
height: 30px;
text-align: center;
}
<div id="custDiv">
<div class="addBtns">
<button id="addCust" onclick="appendRowCust()">add customer</button>
</div>
<div style="width: 355px; margin: 0 auto; height: 50px;">
<button id="sortCust" onclick="sortCustTable()">sort</button>
</div>
<div class="custScroll">
<table id="custListTop">
<tr>
<td contenteditable="false">Customers</td>
<td contenteditable="false">Main Location</td>
</tr>
</table>
<table id="custList" contenteditable="true">
<tr>
<td>Someone</td>
<td>something</td>
</tr>
<tr>
<td>Somebody</td>
<td>1</td>
</tr>
<tr>
<td></td>
<td>1</td>
</tr>
<tr>
<td>0</td>
<td>1</td>
</tr>
<tr>
<td>someone else</td>
<td>1</td>
</tr>
<tr>
<td>somebody else</td>
<td>1</td>
</tr>
</table>
</div>
</div>
Now, to clarify why it sorts this way, let's look at the table values and how we modify them before sorting:
Customers | Main Location
---------------+---------------
Someone | something
Somebody | 1
| 1
0 | 1
someone else | 1
somebody else | 1
We'll discard the second column since we don't use that, and set all the customers to uppercase as well:
Customers |
---------------|
SOMEONE |
SOMEBODY |
|
0 |
SOMEONE ELSE |
SOMEBODY ELSE |
Next we check each string to see if it's empty, if so, we give it the value 'k':
Customers |
---------------|
SOMEONE |
SOMEBODY |
k |
0 |
SOMEONE ELSE |
SOMEBODY ELSE |
Then lastly, we modify any numbers by adding their numeric value to 97, and converting the resulting charCode into a character:
Customers |
---------------|
SOMEONE |
SOMEBODY |
k |
a |
SOMEONE ELSE |
SOMEBODY ELSE |
Sorting lexicographically, we get:
Customers |
---------------|
SOMEBODY |
SOMEBODY ELSE |
SOMEONE |
SOMEONE ELSE |
a |
k |
And putting back in the original values, we get:
Customers | Main Location
---------------+---------------
Somebody | 1
somebody else | 1
Someone | something
someone else | 1
0 | 1
| 1
I added a method to draw your table from an array of cell contents. Sorting the rows and calling drawTableRows will recreate the table in any order the rows array ends up. I added some code to insert mock data every third row so sorting numbers to the bottom can be seen.
This is a bigger change than I usually like to give in answers but I thought you might appreciate seeing a different approach.
var rows = [[ 'Someone', 'something' ]];
function drawTableRows() {
var custList = document.getElementById('custList'); // table reference
custList.innerHTML = '';
for(var i = 0; i < rows.length; i++) {
var row = rows[i];
var tableRow = custList.insertRow(i); // append table row
for(var j = 0; j < row.length; j++) {
createCell(tableRow.insertCell(j), row[j], 'row');
}
}
}
// add customer
function appendRowCust(customer = 0, location = 1) {
//throw in mock data every 3 rows (just a test - remove later)
if(rows.length % 3 === 0) {
customer = 'Real customer ' + rows.length;
location = 'Real location ' + rows.length;
}
rows.push([customer, location]);
drawTableRows();
}
// create DIV element and append to the table cell
function createCell(cell, text, style) {
var div = document.createElement('div'), // create DIV element
txt = document.createTextNode(text); // create text node
div.appendChild(txt); // append text node to the DIV
div.setAttribute('class', style); // set DIV class attribute
div.setAttribute('className', style); // set DIV class attribute for IE (?!)
cell.appendChild(div); // append DIV to the table cell
}
function sortCustTable() {
rows.sort(function(a,b){
//sort by first column
var aVal = a[0];
var bVal = b[0];
//sort by cell content - if content is a number push to bottom.
if((bVal > aVal) || !isNaN(bVal)) {
return -1;
}
if((aVal > bVal) || !isNaN(aVal)) {
return 1;
}
return 0;
});
drawTableRows();
}
table {
background-color: black;
color: white;
}
tr:nth-child(even) {
background-color: #656565;
}
tr:nth-child(odd) {
background-color: #505050;
}
td {
width: 300px;
max-width: 300px;
height: 30px;
text-align: center;
}
<div id="custDiv">
<div class="addBtns">
<button id="addCust" onclick="appendRowCust()">add customer</button>
</div>
<div style="width: 355px; margin: 0 auto; height: 50px;">
<button id="sortCust" onclick="sortCustTable()">sort</button>
</div>
<div class="custScroll">
<table id="custListTop">
<tr>
<td contenteditable="false">Customers</td>
<td contenteditable="false">Main Location</td>
</tr>
</table>
<table id="custList" contenteditable="true">
<tr>
<td>Someone</td>
<td>something</td>
</tr>
</table>
</div>
</div>
You can filter out the differences by attempting to cast to numeric and determining if the current iterated value is an integer. Afterwards simply sort and concatenate the two result sets.
var list = [0, 2, "2", "0", 1, 2, "a", "b", "c"],
numeric = list.filter(value => Number.isInteger(+value)),
alpha = list.filter(value => !Number.isInteger(+value)),
result = alpha.sort().concat(numeric.sort());
To optimize the above you can filter once and push to a separately declared array alpha if the result is false.
var list = [0, 2, "2", "0", 1, 2, "a", "b", "c"],
alpha = [],
numeric = list.filter(value => {
let torf = Number.isInteger(+value);
if (!torf) alpha.push(value);
return torf;
}),
result = alpha.sort().concat(numeric.sort());
Determining the difference between the two would be a micro-optimization that I doubt would be necessary in any situation and the former is more verbose and clear. My suggestion would be to use the first option.
var list = [0, 2, "2", "0", 1, 2, "a", "b", "c"],
numeric = list.filter(value => Number.isInteger(+value)),
alpha = list.filter(value => !Number.isInteger(+value)),
result = alpha.sort().concat(numeric.sort());
console.log(result);

While Loop: Generated table rows and column inconsistency

Please explain why the code I borrowed from another SO question produces inconsistent results. The program accepts 2 inputs, rows and columns. It should then generate a table with the exact amount of table rows and table columns that was input. However, the first row seems to multiply out the cells and decrease each row until the final row actually renders what every other row should be.
Also, I noticed that when I move line 25 inside of the inner while loop, that a table that is akin to a pyramid is generated (which is cool) but I cannot explain what it is doing.
$("<tr class='tableRow'>").appendTo('table#container');
So with that said, please help me generate a table that will evenly render rows and columns when the input is equal.
Note: Entering 1 for rows and 1 for columns does return the intended result, but that is the only scenario where it "works". Entering 2 for rows and 2 for columns, produces unintended results.
Sandbox Fiddle
//Table Generator
var c = 10; //parseInt(prompt("Enter column "), 10);
var r = 10; //parseInt(prompt("Enter row "), 10);
var cTmp = c;
var rTmp = r;
function rowLoop() {
$('tr.tableRow').each(function(index) {
var trFound = $("tr.tableRow:eq(" + index + ")");
var rowNum = parseInt(($("tr.tableRow:eq(" + index + ")").index()), 10);
var tdAdd = "<td>test</td>";
if ($(this).index() === rowNum) {
trFound.append(tdAdd);
console.log("Add a TD");
console.log(rowNum + "=" + $(this).index());
console.log(rowNum + "=" + $(this).index());
} else {
console.log(rowNum + "<>" + $(this).index());
console.log(rowNum + "<>" + $(this).index());
}
});
}
while (0 < rTmp) {
cTmp = c;
$("<tr class='tableRow'>").appendTo('table#container');
while (0 < cTmp) {
rowLoop();
cTmp--;
}
document.getElementById("container").innerHTML = document.getElementById("container").innerHTML + "</tr>";
rTmp--;
}
table {
border-collapse: collapse;
border: solid 1px #ACE;
}
tr {
height: 15px;
}
td {
border: solid 1px red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
<table id='container'></table>
Instead of using a while-loop why don't you just use a simple nested for-loop? This will make this task much easier to read and comprehend.
//Table Generator
var cols = 10; //parseInt(prompt("Enter column "), 10);
var rows = 10; //parseInt(prompt("Enter row "), 10);
generateTable('container', rows, cols, 'test');
function generateTable(id, rows, cols, fill) {
var elTable = document.getElementById(id);
createRows(elTable, rows, cols, fill);
}
function createRows(elTable, rows, cols, fill) {
for (var row = 0; row < rows; row++) {
elTable.appendChild(createRow(row, cols, fill));
}
}
function createRow(row, cols, fill) {
var elRow = document.createElement('tr');
elRow.className = 'tableRow';
createCols(elRow, row, cols, fill);
return elRow;
}
function createCols(elRow, row, cols, fill) {
for (var col = 0; col < cols; col++) {
elRow.appendChild(createCol(row, col, fill));
}
}
function createCol(row, col, fill) {
var elCol = document.createElement('td');
elCol.innerHTML = fill || row + 'x' + col;
return elCol;
}
table {
border-collapse: collapse;
border: solid 1px #ACE;
}
tr {
height: 15px;
}
td {
border: solid 1px red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<table id='container'></table>
jQuery Plugin
You can skip the loop altogether and use a range-map. You can actually define an array using:
Array.apply(null, Array(n)) // Where n is an integer greater than 0
You can then map each item in the array to either a function or the current index in the map function.
map(function(_, idx) {
// Return the current index in the map's "loop" callback.
if (val == null) return idx;
// Execute the value as a function; pass index as 1st param.
if (isFunction(val)) return val.call(null, idx);
// Return the value as an (assumed) scalar value.
return val;
});
Here is a jQuery plugin to generate rows and columns using the explained trick above to generate an array at a desired size with an option default value.
//Table Generator
(function($) {
$.fn.generateTable = function(options) {
var defaults = {
rows: 0,
cols: 0,
fill: function(row, col) {
return row + 'x' + col;
}
};
var opts = $.extend(defaults, options);
function fillArray(n, val) {
return Array.apply(null, Array(n)).map(function(_, idx) {
return val == null ? idx : isFunction(val) ? val.call(null, idx) : val;
});
}
function isFunction(value) {
return typeof value == 'function';
}
return $(this).append(fillArray(opts.rows, function(row) {
return $('<tr>', {
class: 'tableRow'
}).append(fillArray(opts.cols, function(col) {
return $('<td>', {
text: isFunction(opts.fill) ? opts.fill.call(null, row, col) : opts.fill
});
}));
}));
};
}(jQuery));
$(function() {
$('#container').generateTable({
rows: 10, //parseInt(prompt("Enter row count"), 10)
cols: 10, //parseInt(prompt("Enter column count"), 10)
fill: 'test'
});
});
table {
border-collapse: collapse;
border: solid 1px #ACE;
}
tr {
height: 15px;
}
td {
border: solid 1px red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<table id='container'></table>
Stages of Development
Below, you will see how each successive block of code has evolved from its predecessor.
I. Vanilla JavaScript translated to jQuery
//Table Generator
var cols = 10; //parseInt(prompt("Enter column "), 10);
var rows = 10; //parseInt(prompt("Enter row "), 10);
generateTable('#container', rows, cols, 'test');
function generateTable(selector, rows, cols, fill) {
var $el = $(selector)
createRows($el, rows, cols, fill);
}
function createRows($table, rows, cols, fill) {
for (var row = 0; row < rows; row++) {
$table.append(createRow(row, cols, fill));
}
}
function createRow(row, cols, fill) {
var $row = $('<tr>', {
class: 'tableRow'
});
createCols($row, row, cols, fill);
return $row;
}
function createCols($row, row, cols, fill) {
for (var col = 0; col < cols; col++) {
$row.append(createCol(row, col, fill));
}
}
function createCol(row, col, fill) {
return $('<td>', {
text: fill || row + 'x' + col
});
}
table {
border-collapse: collapse;
border: solid 1px #ACE;
}
tr {
height: 15px;
}
td {
border: solid 1px red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<table id='container'></table>
II. Simple jQuery Plugin
//Table Generator
(function($) {
$.fn.generateTable = function(options) {
var $table = $(this);
_createRows($table, options.rows, options.cols, options.fill);
};
_createRows = function($table, rows, cols, fill) {
for (var row = 0; row < rows; row++) {
$table.append(_createRow(row, cols, fill));
}
};
_createRow = function(row, cols, fill) {
var $row = $('<tr>', {
class: 'tableRow'
});
_createCols($row, row, cols, fill);
return $row;
};
_createCols = function($row, row, cols, fill) {
for (var col = 0; col < cols; col++) {
$row.append(_createCol(row, col, fill));
}
};
_createCol = function(row, col, fill) {
return $('<td>', {
text: fill || row + 'x' + col
});
};
}(jQuery));
var cols = 10; //parseInt(prompt("Enter column "), 10);
var rows = 10; //parseInt(prompt("Enter row "), 10);
$(function() {
$('#container').generateTable({
rows: rows,
cols: cols,
fill: 'test'
});
});
table {
border-collapse: collapse;
border: solid 1px #ACE;
}
tr {
height: 15px;
}
td {
border: solid 1px red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<table id='container'></table>
III. Advanced jQuery Plugin
//Table Generator
(function($) {
$.fn.generateTable = function(options) {
$(this).append(_fillArray(options.rows, function(row) {
return $('<tr>', {
class: 'tableRow'
}).append(_fillArray(options.cols, function(col) {
return $('<td>', {
text: options.fill || row + 'x' + col
});
}));
}));
};
function _fillArray(n, defaultValue) {
return Array.apply(null, Array(n)).map(function(val, idx) {
if (defaultValue === undefined) return idx;
if (typeof defaultValue == 'function') return defaultValue.call(null, idx);
return defaultValue;
});
}
}(jQuery));
var colCount = 10; //parseInt(prompt("Enter column "), 10);
var rowCount = 10; //parseInt(prompt("Enter row "), 10);
$(function() {
$('#container').generateTable({
rows: rowCount,
cols: colCount,
fill: 'test'
});
});
table {
border-collapse: collapse;
border: solid 1px #ACE;
}
tr {
height: 15px;
}
td {
border: solid 1px red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<table id='container'></table>
Your problem likely stems from the fact that within each rowLoop() execution, you are using this as selector for iteration:
$('tr.tableRow')
This will grab all rows that are currently in DOM for iteration, not just the current row you are working on.
A few options:
Build a single row object with X number of columns first, then make clones of it and append to table for Y number of rows.
Build Y empty TR's first, then iterate over each TR, appending X TD's.

Javascript cut table to insert a tag <div>

I want to cut table after tr to insert a div and re-open the table :
Before :
<table>
<tr onClick="cutAfter(this);">
<td>bla</td><td> 123 </td><td>Yes </td>
</tr>
<tr onClick="cutAfter(this);">
<td>bli</td><td> 456 </td><td>no</td>
</tr>
<tr onClick="cutAfter(this);">
<td>blu</td><td> 789 </td><td>hum</td>
</tr>
</table>
After :
<table>
<tr onClick="cutAfter(this);">
<td>bla</td><td> 123 </td><td>Yes </td>
</tr>
<tr onClick="cutAfter(this);">
<td>bli</td><td> 456 </td><td>no</td>
</tr>
</table>
<div onClick="fuse(this)">It works</div>
<table>
<tr onClick="cutAfter(this);">
<td>blu</td><td> 789 </td><td>hum</td>
</tr>
</table>
And return to first state on click.
Any idea (no jQuery).
A simple whay to do it is to use combination of DOM manipulation methods like insertAdjacentHTML to create new table and appendChild to move rows into new table:
function cutAfter(row) {
var table = row.parentNode.parentNode;
if (row.nextElementSibling) {
table.insertAdjacentHTML('afterend', '<table><tbody></tbody></table>');
var newTable = table.nextElementSibling.tBodies[0];
while (row.nextElementSibling) {
newTable.appendChild(row.nextElementSibling);
}
}
}
table {
margin-bottom: 10px;
}
table td {
border: 1px #AAA solid;
}
<table>
<tr onClick="cutAfter(this);">
<td>bla</td><td> 123 </td><td>Yes </td>
</tr>
<tr onClick="cutAfter(this);">
<td>bli</td><td> 456 </td><td>no</td>
</tr>
<tr onClick="cutAfter(this);">
<td>blu</td><td> 789 </td><td>hum</td>
</tr>
</table>
Here's a simple example, made of an HTML file (the structure) and a Javascript file (the behavior). The script uses node manipulation, so as to preserve existing handlers (e.g. added by other scripts). It also attaches event handlers directly, using element.onxxx, to keep things simple, but you should replace that by your favorite event manager.
var makeSplittable = function(table, joinText) {
init();
function init() {
var tBodies = table.tBodies;
for (var ii = 0; ii < tBodies.length; ii++) {
var rows = tBodies[ii].rows;
for (var j = 0; j < rows.length; j++) {
rows[j].onclick = split; // Replace with your favorite event manager
}
}
}
function split(evt) {
var rowIndex = this.rowIndex;
var tbody = findParent(this, "tbody");
var numRows = tbody.rows.length;
if (rowIndex < numRows - 1) {
var rows = [];
for (var ii = rowIndex + 1; ii < numRows; ii++) {
rows.push(tbody.rows[ii]);
}
var existingTable = findParent(this, "table");
var newTable = createTable(rows);
var joiner = createJoiner();
existingTable.parentNode.insertBefore(newTable, existingTable.nextSibling);
existingTable.parentNode.insertBefore(joiner, existingTable.nextSibling);
}
}
function createTable(rows) {
var table = document.createElement("table");
var tbody = document.createElement("tbody");
for (var ii = 0; ii < rows.length; ii++) {
tbody.appendChild(rows[ii]);
}
table.appendChild(tbody);
return table;
}
function createJoiner() {
var div = document.createElement("div");
var content = document.createTextNode(joinText);
div.appendChild(content);
div.onclick = join; // same
return div;
}
function join(evt) {
var previousTable = this.previousSibling;
var nextTable = this.nextSibling;
var tbody = previousTable.tBodies[previousTable.tBodies.length - 1];
var rows = nextTable.rows;
while (rows.length) {
tbody.appendChild(rows[0]);
}
nextTable.parentNode.removeChild(nextTable);
this.parentNode.removeChild(this);
}
function findParent(element, type) {
if (!element || !type) {
return null;
}
if (element.nodeName.toLowerCase() == type.toLowerCase()) {
return element;
}
return findParent(element.parentNode, type);
}
};
makeSplittable(document.getElementById("target"), "Merge adjacent tables");
table,
div {
margin: 5px 0;
}
tr:hover td {
background-color: orange;
}
td {
background-color: yellow;
cursor: pointer;
padding: 10px;
}
div {
color: #0c0;
cursor: pointer;
}
<table id="target">
<tr>
<td>bla</td>
<td>123</td>
<td>Yes</td>
</tr>
<tr>
<td>bli</td>
<td>456</td>
<td>no</td>
</tr>
<tr>
<td>blu</td>
<td>789</td>
<td>hum</td>
</tr>
</table>
A possibility, assuming that a DIV should not be inserted after the last TR if there has not been a cut, but it would have been nice to see your effort. Also assuming no <thead> or <tfoot>.
function isTagName(element, tagName) {
return element.tagName.toUpperCase() === tagName.toUpperCase();
}
function getClosest(element, tagName) {
var closest = null;
while (element !== document && !isTagName(element, tagName)) {
element = element.parentNode;
}
if (element !== document && isTagName(element, tagName)) {
closest = element;
}
return closest;
}
function insertAfter(newNode, referenceNode) {
return referenceNode.parentNode
.insertBefore(newNode, referenceNode.nextSibling);
}
function moveAppend(list, dest, from) {
var index = list.length - 1,
last;
for (last = from || 0; index >= last; index -= 1) {
dest.appendChild(list[index]);
}
return dest;
}
document.body.addEventListener('click', function(e) {
var target = e.target,
tr = getClosest(target, 'tr'),
newDiv,
newTable,
newBody,
next,
parent;
if (tr) {
if (tr.rowIndex < tr.parentNode.rows.length - 1) {
newDiv = document.createElement('div');
newDiv.appendChild(document.createTextNode('It works!'));
insertAfter(newDiv, getClosest(tr, 'table'));
newTable = document.createElement('table');
newBody = document.createElement('tbody');
moveAppend(tr.parentNode.rows, newBody, tr.rowIndex + 1);
newTable.appendChild(newBody);
insertAfter(newTable, newDiv);
}
} else if (isTagName(target, 'div') &&
isTagName(target.previousElementSibling, 'table') &&
isTagName(target.nextElementSibling, 'table')) {
next = target.nextElementSibling;
moveAppend(next.tBodies[0].rows, target.previousElementSibling.tBodies[0]);
parent = target.parentNode;
parent.removeChild(next);
parent.removeChild(target);
}
}, false);
table,
td {
border-style: solid;
border-width: 1px;
}
div {
background-color: yellow;
}
<table>
<tr>
<td>bla</td>
<td>123</td>
<td>Yes</td>
</tr>
<tr>
<td>bli</td>
<td>456</td>
<td>no</td>
</tr>
<tr>
<td>blu</td>
<td>789</td>
<td>hum</td>
</tr>
</table>

Categories