Древовидная таблица HTML + CSS + JS
Всем Добрый вечер! Прошу помощи с вёрсткой. Хочу сделать древовидную таблицу, (данные могут меняться) но у меня постоянно слезает стиль в право. Имею такой вариант https://jsfiddle.net/nafanya123/L9txe6bq/
<body>
<button onclick="SwapAll(false);">Свернуть всё</button>   <button onclick="SwapAll(true);">Развернуть всё</button>
<br><br>
<table id="MyTable">
<tr>
<th>Name</th>
<th>sum1</th>
<th>sum2</th>
<th>sum3</th>
</tr>
<tr>
<td>
<label>
<input type="checkbox">
<a onclick="sh(this)">Item1</a>
</label>
</td>
<td>313</td>
<td>5</td>
<td>318</td>
</tr>
<tr>
<td colspan=5 class="node">
<input type="checkbox">
<table>
<tr>
<td>
<label>
<input type="checkbox">
<a onclick="sh(this)">Item2</a>
</label>
</td>
<td>313</td>
<td>5</td>
<td>318</td>
</tr>
</table>
<table>
<tr>
<td>
<label>
<input type="checkbox">
<a onclick="sh(this)">Item3</a>
</label>
</td>
<td>313</td>
<td>5</td>
<td>318</td>
</tr>
<tr>
<td colspan=5 class="node">
<input type="checkbox">
<table>
<tr>
<td>
<label>
<input type="checkbox">
<a onclick="sh(this)">Item4</a>
</label>
</td>
<td></td>
<td></td>
<td>10</td>
</tr>
<tr>
<td colspan=5 class="node">
<input type="checkbox">
<table>
<tr>
<td>
<label>
<input type="checkbox">
<a onclick="sh(this)">Item5</a>
</label>
</td>
<td></td>
<td></td>
<td>10</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td>
test
</td>
<td>313</td>
<td>5</td>
<td>318</td>
</tr>
</table>
table {
border-collapse:collapse;
table-layout:fixed;
width:800px;
}
td, th {
border:1px solid gray;
border-top:0;
padding:2px 5px;
width:300px;
}
th {
background:#ccc;
border-top:1px solid gray;
}
input{
display:none;
}
input + a {
background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAALCAIAAAAmzuBxAAAACXBIWXMAAAsSAAALEgHS3X78AAAAkElEQVQYlXWOvRWDQAyDv/DYK2wQSro8OkpGuRFcUjJCRmEE0TldCpsjPy9qzj7Jki62Pgh4vnqbbbEWuN+use/PlArwHccWGg780psENGFY6W4YgxZIAM339WmT3m397YYxxn6aASslFfVotYLTT3NwcuTKlFpNR2sdEak4acdKeafPlE2SZ7sw/1BEtX94AXYTVmyR94mPAAAAAElFTkSuQmCC)
no-repeat 0px 4px;
}
input:checked + a {
background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAALCAIAAAAmzuBxAAAACXBIWXMAAAsSAAALEgHS3X78AAAAeklEQVQYlX2PsRGDMAxFX3zeK9mAlHRcupSM4hFUUjJCRpI70VHIJr7D8BtJ977+SQ9Zf7isVG16WSQC0/D0OW/FqoBlDFkIVJ2xAhA8sI/NHbcYiFrPfI0fGklKagDx2F4ltdtaM0J9L3dxcVxi+zv62E+MwPs7c60dClRP6iug7wUAAAAASUVORK5CYII=)
no-repeat 0px 4px;
}
label a {
cursor:pointer;
padding-left:16px;
}
input ~ table {
display:none;
}
input:checked ~ table {
display:table;
margin-top:-1px;
margin-left:-1px;
}
input:checked ~ table{
margin-left: 15px;
}
.node {
padding:0;
border-bottom:0;
}
td+td+td+td {
text-align:right;
}
function sh(el) {
var row = el.parentNode.parentNode.parentNode;
var nextRow = row.parentNode.rows[row.rowIndex + 1];
var chk = nextRow.querySelector('td > input');
chk.checked = !chk.checked;
}
function SwapAll(b) {
var tbl = document.getElementById('MyTable');
var chks = tbl.getElementsByTagName('input');
for (var i=0; i<chks.length; i++) {
chks[i].checked = b;
}
}
Хочу добиться такого варианта:

Ответы (2 шт):
Автор решения: zhurof
→ Ссылка
На мой взгляд лучше разместить все уровни вложенности на одном уровне dom-дерева. Это обеспечит стабильное поведение ячеек таблицы даже без задания им ширины
[].forEach.call(document.querySelectorAll('.toggler input'), function(input) {
input.addEventListener('change', function() {
var row = input.closest('tr');
if (input.checked) {
showChildRows(row);
} else {
hideChildRows(row);
}
})
});
//получить дочерние ряды всех уровней вложенности
function getChildRows(row) {
var childRows = [],
currentRow = row,
startLevel = +row.getAttribute('data-level');
while (currentRow && currentRow.nextElementSibling && +currentRow.nextElementSibling.getAttribute('data-level') > startLevel) {
childRows.push(currentRow.nextElementSibling);
currentRow = currentRow.nextElementSibling;
}
return childRows;
}
//скрыть все дочерние ряды
function hideChildRows(row) {
var childRows = getChildRows(row);
childRows.forEach(function(childRow) {
childRow.classList.remove('dropdown--visible')
})
}
//показать дочерние ряды в зависимости от состояния чекбокса
function showChildRows(row) {
var childRows = getChildRows(row), //все дочерние ряды
level = +row.getAttribute('data-level'),
toggler = row.querySelector('.toggler input'),
immediateChildren = childRows.filter(function(tr) {
return tr.getAttribute('data-level') == level + 1
}); //непосредственно дочерние ряды
if (toggler && toggler.checked) { //если ряд раскрыт, показываем непосредственно дочерние ряды и повторяем для них такую же процедуру
immediateChildren.forEach(function(childRow) {
childRow.classList.add('dropdown--visible')
showChildRows(childRow); //типа рекурсия
})
}
}
//кнопки
document.getElementById('slideUp').addEventListener('click', function() {
[].forEach.call(document.querySelectorAll('.toggler input'), function(input) {
input.checked = false
});
[].forEach.call(document.querySelectorAll('.dropdown--visible'), function(row) {
row.classList.remove('dropdown--visible')
});
})
document.getElementById('slideDown').addEventListener('click', function() {
[].forEach.call(document.querySelectorAll('.toggler input'), function(input) {
input.checked = true
});
[].forEach.call(document.querySelectorAll('.dropdown'), function(row) {
row.classList.add('dropdown--visible')
});
})
table {
border-collapse: collapse;
width: 800px;
max-width: 100%;
}
td,
th {
border: 1px solid gray;
border-top: 0;
padding: 2px 5px;
width: 300px;
}
th {
background: #ccc;
border-top: 1px solid gray;
}
tbody td:last-child {
text-align: right;
}
.toggler {
display: inline-block;
user-select: none;
cursor: pointer;
}
.toggler input {
margin: 0 .75em 0 0;
appearance: none;
-webkit-apppearance: none;
display: inline-block;
width: 11px;
height: 11px;
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAALCAIAAAAmzuBxAAAACXBIWXMAAAsSAAALEgHS3X78AAAAkElEQVQYlXWOvRWDQAyDv/DYK2wQSro8OkpGuRFcUjJCRmEE0TldCpsjPy9qzj7Jki62Pgh4vnqbbbEWuN+use/PlArwHccWGg780psENGFY6W4YgxZIAM339WmT3m397YYxxn6aASslFfVotYLTT3NwcuTKlFpNR2sdEak4acdKeafPlE2SZ7sw/1BEtX94AXYTVmyR94mPAAAAAElFTkSuQmCC) no-repeat center/contain;
}
.toggler input:checked {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAALCAIAAAAmzuBxAAAACXBIWXMAAAsSAAALEgHS3X78AAAAeklEQVQYlX2PsRGDMAxFX3zeK9mAlHRcupSM4hFUUjJCRpI70VHIJr7D8BtJ977+SQ9Zf7isVG16WSQC0/D0OW/FqoBlDFkIVJ2xAhA8sI/NHbcYiFrPfI0fGklKagDx2F4ltdtaM0J9L3dxcVxi+zv62E+MwPs7c60dClRP6iug7wUAAAAASUVORK5CYII=);
}
.dropdown {
display: none;
}
.dropdown--visible {
display: table-row;
}
.dropdown td:first-child {
padding-left: calc(5px + 15px * var(--level));
}
<button id="slideUp">Свернуть всё</button>   <button id="slideDown">Развернуть всё</button>
<br><br>
<table id="MyTable">
<thead>
<tr>
<th>Name</th>
<th>sum1</th>
<th>sum2</th>
<th>sum3</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<label class="toggler"><input type="checkbox"><span>Item1</span></label>
</td>
<td>313</td>
<td>5</td>
<td>318</td>
</tr>
<tr class="dropdown" style="--level: 1" data-level="1">
<td>
<label class="toggler"><input type="checkbox"><span>Item2</span></label>
</td>
<td>313</td>
<td>5</td>
<td>318</td>
</tr>
<tr class="dropdown" style="--level: 1" data-level="1">
<td>
<label class="toggler"><input type="checkbox"><span>Item3</span></label>
</td>
<td>313</td>
<td>5</td>
<td>318</td>
</tr>
<tr class="dropdown" style="--level: 2" data-level="2">
<td>
<label class="toggler"><input type="checkbox"><span>Item4</span></label>
</td>
<td></td>
<td></td>
<td>10</td>
</tr>
<tr class="dropdown" style="--level: 3" data-level="3">
<td>
<label class="toggler"><input type="checkbox"><span>Item5</span></label>
</td>
<td></td>
<td></td>
<td>10</td>
</tr>
<tr>
<td>test</td>
<td>313</td>
<td>5</td>
<td>318</td>
</tr>
</tbody>
</table>
Автор решения: UserTest013
→ Ссылка
Соглашусь с @zhurof, лучше разместить все уровни на одном уровне DOM. Но мне кажется, что реализация слишком сложная. Вот мой вариант, как альтернативный
const table = document.querySelector('table');
const setLevel = (tr, level) => {
tr.style.setProperty('--padding-left', `${level * 15 + 5}px`);
[...table.querySelectorAll(`tr[data-parent='${tr.querySelector('span').innerText}']`)].forEach((tr) => setLevel(tr, level + 1));
};
const toggleState = (tr, expanded, visible, main = false) => {
if (main) {
tr.classList.toggle('expanded');
visible = tr.classList.contains('expanded');
}
expanded = tr.classList.contains('expanded');
[...table.querySelectorAll(`tr[data-parent='${tr.querySelector('span').innerText}']`)].forEach((row) => {
visible && expanded ? row.classList.add('visible') : row.classList.remove('visible');
toggleState(row, expanded, visible);
});
};
[...table.querySelectorAll('.row:not([data-parent])')].forEach((tr) => setLevel(tr, 0));
[...table.querySelectorAll('button')].forEach((button) => button.addEventListener('click', (event) => toggleState(event.target.closest('tr'), undefined, undefined, true)));
table {
border-collapse: collapse;
table-layout: fixed;
width: 100%;
}
td,
th {
border: 1px solid gray;
padding: 5px;
min-width: 100px;
}
th {
background: #ccc;
}
tr[data-parent] {
display: none;
}
.row[data-parent].visible {
display: table-row;
}
.row td:first-child {
padding-left: var(--padding-left);
}
.row button {
width: 13px;
height: 13px;
line-height: 0;
outline: none;
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAALCAIAAAAmzuBxAAAACXBIWXMAAAsSAAALEgHS3X78AAAAkElEQVQYlXWOvRWDQAyDv/DYK2wQSro8OkpGuRFcUjJCRmEE0TldCpsjPy9qzj7Jki62Pgh4vnqbbbEWuN+use/PlArwHccWGg780psENGFY6W4YgxZIAM339WmT3m397YYxxn6aASslFfVotYLTT3NwcuTKlFpNR2sdEak4acdKeafPlE2SZ7sw/1BEtX94AXYTVmyR94mPAAAAAElFTkSuQmCC) no-repeat;
}
.row.expanded button {
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAALCAIAAAAmzuBxAAAACXBIWXMAAAsSAAALEgHS3X78AAAAeklEQVQYlX2PsRGDMAxFX3zeK9mAlHRcupSM4hFUUjJCRpI70VHIJr7D8BtJ977+SQ9Zf7isVG16WSQC0/D0OW/FqoBlDFkIVJ2xAhA8sI/NHbcYiFrPfI0fGklKagDx2F4ltdtaM0J9L3dxcVxi+zv62E+MwPs7c60dClRP6iug7wUAAAAASUVORK5CYII=) no-repeat
}
<table>
<tr>
<th>Name</th>
<th>sum1</th>
<th>sum2</th>
<th>sum3</th>
</tr>
<tr class="row">
<td>
<button> </button>
<span>Item1</span>
</td>
<td>313</td>
<td>5</td>
<td>318</td>
</tr>
<tr class="row" data-parent="Item1">
<td>
<button> </button>
<span>Item2</span>
</td>
<td>313</td>
<td>5</td>
<td>318</td>
</tr>
<tr class="row" data-parent="Item2">
<td>
<button> </button>
<span>Item3</span>
</td>
<td>313</td>
<td>5</td>
<td>318</td>
</tr>
<tr class="row" data-parent="Item2">
<td>
<button> </button>
<span>Item4</span>
</td>
<td>313</td>
<td>5</td>
<td>318</td>
</tr>
<tr class="row">
<td>
<button> </button>
<span>Item5</span>
</td>
<td>313</td>
<td>5</td>
<td>318</td>
</tr>
</table>