Я создаю сниппет, который может добавлять любое количество строк (без защиты от ошибок):
// initial data
// container for keepeng items
let data = [{
id: 0,
size: 10
},
{
id: 1,
size: 12
},
]
// START: DOM manipulation functions
const cellTemplate = (cell) => {
return `<td>${ cell }</td>`
}
const rowTemplate = (row) => {
let html = ''
html += '<tr>'
Object.entries(row).forEach(([key, value]) => {
html += cellTemplate(value)
})
html += `<td><button class="removeRow" data-rowid=${ row.id }>Remove</button></td>`
html += '</tr>'
return html
}
const tbodyTemplate = (data) => {
let html = ''
data.forEach(e => {
html += rowTemplate(e)
})
return html
}
const updateTbody = (data) => {
jQuery("tbody").html(tbodyTemplate(data))
}
// END: DOM manipulation functions
// calculating new ID (very sloppy, but it's OK for a snippet)
const nextId = (data) => {
const nextIdx = Math.max.apply(null, data.map(({ id }) => id)) + 1
return nextIdx < 0 ? 0 : nextIdx
}
// adding ANY number of items to the
// item container (data)
const addItem = (data, number) => {
const d = JSON.parse(JSON.stringify(data))
for (let i = 0; i < number; i++) {
d.push({
id: nextId(d),
size: 9 + i
})
}
return d
}
// init view
jQuery(document).ready(function($) {
updateTbody(data)
// add one row
$('.addRow').on('click', function() {
data = addItem(data, 1)
updateTbody(data) // updating DOM with the new dataset
})
// add number of rows based on input
$('.addMoreRows').on('click', function() {
const numRows = $('#rowNum').val()
data = addItem(data, numRows)
updateTbody(data) // updating DOM with the new dataset
})
// remove one row
// watch out for the dynamic binding!
$('body').on('click', '.removeRow', function() {
const id = Number($(this).attr("data-rowid"))
const idx = data.indexOf(data.find(e => e.id === id))
data.splice(idx, 1)
updateTbody(data) // updating DOM with the new dataset
})
})
table {
border-collapse: collapse;
}
table,
tr,
th,
td {
border: 1px solid black;
}
thead {
background: gray;
border-bottom: 2px solid black;
}
tbody tr:nth-child( 2n) {
background: lightgray
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<button class="addRow">
Add New Row
</button><br />
<button class="addMoreRows">
Add New Rows
</button>
<label for="rowNum">How many rows do you want to add?<input id="rowNum" type="number" value=1 min=1 max=10 /></label>
<table>
<thead>
<tr>
<th>ID</th>
<th>Size</th>
<th>Manage</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
Основная идея состоит в том, что ничто не отслеживает количество элементов (явно), но все основано на «одном источнике истины»: массив data
(объектов). Если вы манипулируете этим массивом и обновляете DOM на основе управляемого массива, тогда таблица изменяется соответствующим образом.
Если вы изменяете контейнер элемента (data
) и шаблоны (tbody
, row
, cell
) в соответствии с вашими требованиями к презентации, тогда вы можете добавлять и удалять любое количество строк - не погружаясь в хаос.
Вы можете создавать подэлементы, подподпункты элементы и функции, которые удаляют только один подпункт или основной элемент целиком. Лог c прост: только модифицируйте данные, а затем обновите DOM на основе шаблонов. (Это означает: сначала создайте структуру данных, функции, которые могут управлять этой структурой, как вам нужно, и только затем оберните ее в таблицу с кнопками.)
UPDATE
1. Создайте структуру данных, которая представляет ваш случай
let data = [{
id: 0,
size: 10,
color: "red",
price: 12,
variants: [{
size: 12
},
{
size: 14
}
]
},
{
id: 1,
size: 10,
color: "blue",
price: 15,
variants: [{
size: 12
},
{
size: 14
}
]
}
]
Приведенная выше структура данных показывает, что у нас есть два элемента, и у каждого элемента есть еще два варианта (подпункты).
2. Создайте функции, которые изменяют (мутируют) структуру:
let data = [{
id: 0,
size: 10,
color: "red",
price: 12,
variants: [{
id: "0_1",
size: 12
},
{
id: "0_2",
size: 14
}
]
},
{
id: 1,
size: 10,
color: "blue",
price: 15,
variants: [{
id: "1_1",
size: 12
},
{
id: "1_2",
size: 14
}
]
}
]
// mocking a new item
const newItem = {
id: 2,
size: 10,
color: "orange",
price: 8,
variants: [{
id: "2_1",
size: 12
},
{
id: "2_2",
size: 14
}
]
}
const addItem = (data, newItem) => {
let d = JSON.parse(JSON.stringify(data))
d = [...d, newItem]
return d
}
const addSubItem = (data, itemId, subItem) => {
let d = JSON.parse(JSON.stringify(data))
let item = d.find(({
id
}) => id === itemId)
item.variants = [...item.variants, subItem]
return d
}
const removeItem = (data, itemId) => {
let d = JSON.parse(JSON.stringify(data))
const idx = d.indexOf(d.find(({ id }) => id == itemId))
if (idx !== -1) {
d.splice(idx, 1)
}
return d
}
const removeSubItem = (data, subItemId) => {
let d = JSON.parse(JSON.stringify(data))
d.forEach(({ variants }) => {
const variant = variants.find(({ id }) => id === subItemId)
if (variant) {
const idx = variants.indexOf(variant)
if (idx !== -1) {
variants.splice(idx, 1)
}
}
})
return d
}
// original data
console.log('original data:', data)
// adding item
data = addItem(data, newItem)
console.log('after adding new item:', data)
// adding sub-item
data = addSubItem(data, 1, {
id: "1_3",
size: 15
})
console.log('after adding new sub-item:', data)
// removing item
data = removeItem(data, 0)
console.log('after removing item:', data)
// removing sub-item
data = removeSubItem(data, "2_1")
console.log('after removing sub-item:', data)
Хорошо, теперь у нас есть четыре функции для изменения данных:
addItem
addSubItem
removeItem
removeSubItem
3. Оберните его в HTML
let data = [{
id: 0,
size: 10,
color: "red",
price: 12,
variants: [{
id: "0_1",
size: 12
},
{
id: "0_2",
size: 14
}
]
},
{
id: 1,
size: 10,
color: "blue",
price: 15,
variants: [{
id: "1_1",
size: 12
},
{
id: "1_2",
size: 14
},
{
id: "1_2",
color: "beige"
}
]
}
]
const headers = ["id", "size", "color", "price"]
const createTableArray = (data, headers) => {
const items = data.map(item => {
const itemRow = headers.map(header => {
return item[header]
})
const subItemRows = item.variants.map(variant => {
return headers.map(header => {
return variant[header] || ''
})
})
return [itemRow, ...subItemRows]
})
return [headers, ...items]
}
const tableTemplate = (tableArray) => {
let html = ''
tableArray.forEach((e, i, d) => {
let row = ''
if (!i) {
row += '<tr>'
e.forEach(attrs => {
row += `<th>${ attrs }</th>`
})
row += `<th>manage</th>`
row += '</tr>'
} else {
e.forEach(attrs => {
row += `<tr>`
attrs.forEach(attr => {
row += `<td>${ attr }</td>`
})
row += `<td><button class="removeItem" data-itemid="${ attrs[0] }">REMOVE</button></td>`
row += '</tr>'
})
}
html += row
})
return html
}
const updateTable = (tableArray) => {
document.querySelector('table').innerHTML = tableTemplate(tableArray)
}
const tableArray = createTableArray(data, headers)
updateTable(tableArray)
// mocking a new item
const newItem = {
id: 2,
size: 10,
color: "orange",
price: 8,
variants: [{
id: "2_1",
size: 12
},
{
id: "2_2",
size: 14
}
]
}
const addItem = (data, newItem) => {
let d = JSON.parse(JSON.stringify(data))
d = [...d, newItem]
return d
}
const addSubItem = (data, itemId, subItem) => {
let d = JSON.parse(JSON.stringify(data))
let item = d.find(({ id }) => id === itemId)
item.variants = [...item.variants, subItem]
return d
}
const removeItem = (data, itemId) => {
let d = JSON.parse(JSON.stringify(data))
const idx = d.indexOf(d.find(({ id }) => id == itemId))
if (idx !== -1) {
d.splice(idx, 1)
}
return d
}
const removeSubItem = (data, subItemId) => {
let d = JSON.parse(JSON.stringify(data))
d.forEach(({ variants }) => {
const variant = variants.find(({ id }) => id === subItemId)
if (variant) {
const idx = variants.indexOf(variant)
if (idx !== -1) {
variants.splice(idx, 1)
}
}
})
return d
}
jQuery(document).ready(function($) {
$("#addItem").on('click', function() {
data = addItem(data, newItem)
updateTable(createTableArray(data, headers))
})
$("body").on('click', '.removeItem', function() {
const itemId = $(this).data("itemid")
const before = data.length
data = removeItem(data, itemId)
if (before === data.length) {
data = removeSubItem(data, itemId)
}
updateTable(createTableArray(data, headers))
})
})
table {
border-collapse: collapse;
}
table, tr, td, th {
border: 1px solid black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<button id="addItem">ADD ONE ITEM</button><br />
<table>
</table>
Теперь мы там:
- структура данных готова
- данные могут быть изменены ( элементы и вложенные элементы добавлены и удалены)
- Пользовательский интерфейс (HTML) на месте (добавление одного элемента, удаление вложенных элементов по строке, удаление элементов вместе с его вложенными элементами)
Есть некоторые проблемы со сниппетом (ID строк связан с позицией ID в массиве), никакие подпункты (варианты) не могут быть добавлены через UI. Но я думаю, что если немного поработать, его можно будет модифицировать, чтобы сделать все, что вам нужно.