I developed clicking on the parent node to display its child row. I just need to enable to click on the child data should open its sub child rows as recursive one or table tree.
Updated answer
I've changed almost everything and simplified the code:
I've created a repository with that code on GitHub:
https://github.com/TakitIsy/table-to-tree
/* ---- < MAIN FUNCTION > ---- */
function tableToTree(table_Selector, tr_OpenedClass, tr_VisibleClass, tr_ToggleButton) {
// Table elements variables
var table = document.querySelector(table_Selector);
var trs = document.querySelectorAll(table_Selector + " tr");
// Add the buttons above the table
var buttons = document.createElement('div');
buttons.innerHTML = '';
table.insertBefore(buttons, table.childNodes[0]);
buttons = buttons.querySelectorAll('button');
// Add the actions of these buttons
buttons[0].addEventListener("click", function() {
trs.forEach(function(elm) {
elm.classList.remove(tr_OpenedClass);
elm.classList.remove(tr_VisibleClass);
});
});
buttons[1].addEventListener("click", function() {
trs.forEach(function(elm) {
if (elm.innerHTML.includes(tr_ToggleButton))
elm.classList.add(tr_OpenedClass);
elm.classList.add(tr_VisibleClass);
});
});
// Next tr function
function nextTr(row) {
while ((row = row.nextSibling) && row.nodeType != 1);
return row;
}
// On creation, automatically add toggle buttons if the tr has childs elements
trs.forEach(function(tr, index) {
if (index < trs.length - 1) {
if (+tr.getAttribute("level") < +trs[index + 1].getAttribute("level")) {
var elm1 = tr.firstElementChild;
elm1.innerHTML = tr_ToggleButton + elm1.innerHTML;
}
}
});
// Use the buttons added by the function above
table.addEventListener("click", function(e) {
// Event management
if (!e) return;
if (e.target.outerHTML !== tr_ToggleButton) return;
e.preventDefault();
// Get the parent tr and its level
var row = e.target.closest("tr");
row.classList.toggle(tr_OpenedClass);
var lvl = +(row.getAttribute("level"));
// Loop to make childs visible/hidden
while ((row = nextTr(row)) && ((+(row.getAttribute("level")) == (lvl + 1)) || row.className.includes(tr_VisibleClass))) {
row.classList.remove(tr_OpenedClass);
row.classList.toggle(tr_VisibleClass);
}
});
}
/* ---- MAIN FUNCTION > ---- */
// Call the above main function to make the table tree-like
tableToTree('#myTable', 'opened', 'visible', '');
tbody tr {
display: none;
}
tr[level="0"],
tr.visible {
display: table-row;
}
td {
background: #ccc;
padding: 4px 8px 4px 32px;
text-align: left;
}
tr[level="1"] td {
background: #ffffd;
padding-left: 40px;
}
tr[level="2"] td {
background: #eee;
padding-left: 48px;
}
tr .toggle {
position: absolute;
left: 16px;
cursor: pointer;
}
.toggle::after {
content: "[+]";
}
.opened .toggle::after {
content: "[‒]";
}
Parent 1
Match 1
Match 2
Parent 2
Mismatch 1
Mismatch 2
Mismatch 2.1
⋅ ⋅ ⋅
Old answer
I played a little with your code…
Trying to use as many as possible of existing functions/methods to make the code cleaner and easier to read and understand.
… and ended-up with that snippet:
(See comments in my code for more details)
document.getElementById("products").addEventListener("click", function(e) {
// I think using the not equal is nicer here, just my opinion… Less indenting.
if (!e) return; // Exit if null
if (e.target.tagName !== "A") return; // Exit if not A element
e.preventDefault();
var row = e.target.closest("tr"); // Better than parent parent!
var cls = row.classList[0]; // Get the first class name (the only one in our html)
var lvl = +(cls.slice(-1)) + 1; // Unary operator +1 on the last character
cls = cls.slice(0, -1) + lvl; // Replace the last char with lvl to get the class to be toggled
// Check if the row is of the class to be displayed OR if the row is already opened
// (so that clicking close on parent also closes sub-childs)
while ((row = nextTr(row)) && (row.className.includes(cls) || row.className.includes("open")))
row.classList.toggle("open"); // Better than the function you created, it already exists!
});
function nextTr(row) {
while ((row = row.nextSibling) && row.nodeType != 1);
return row;
}
// Select all the tr childs elements (all levels except 0
var allChilds = document.querySelectorAll("tr[class^=level]:not(.level0)");
// Added the below for buttons after comments
document.getElementById("openAll").addEventListener("click", function() {
allChilds.forEach(function(elm){
elm.classList.add("open");
});
});
document.getElementById("closeAll").addEventListener("click", function() {
allChilds.forEach(function(elm){
elm.classList.remove("open");
});
});
tbody tr {
display: none;
}
/* Simplified */
tr.level0,
tr.open {
display: table-row;
}
/* Added colors for better visibility */
tr.level0 {
background: #ccc;
}
tr.level1 {
background: #ffffd;
}
tr.level2 {
background: #eee;
}
/* Added some more styling after comment */
tr td {
padding: 0.2em 0.4em;
}
tr td:first-of-type {
position: relative;
padding: 0.2em 1em;
}
tr td a {
color: inherit;
/* No special color */
text-decoration: inherit;
/* No underline */
position: absolute;
left: 0.25em;
}
tr.level1 td:first-of-type {
padding-left: 1.5em;
}
tr.level2 td:first-of-type {
padding-left: 2em;
}
Product
Price
Destination
Updated on
+Oranges
100
On Store
22/10
121
120
City 1
22/10
+212
140
City 2
22/10
212
140
City 2
22/10
212
140
City 2
22/10
+Apples
100
On Store
22/10
120
120
City 1
22/10
+120
140
City 2
22/10
120
140
City 2
22/10
120
140
City 2
22/10
I hope it helps!