JavaScript动态表格多行功能复用及新增行总价计算失效问题排查与解决
Hey there! Let's work through your table issues. You've got two main problems here: duplicate id attributes (which break HTML standards and your calculation logic) and a calculation function that only targets the first row. Here's how to fix both:
Problem Breakdown
- Duplicate IDs: Your current code uses
id='qty',id='cost',id='total'for every new row. Since HTML requires unique IDs,document.getElementById()will always grab the first matching element—so your calculation only works on the first row. - Hardcoded Event Logic: Using
oninput='calculate()'directly in the HTML works for existing elements, but your function isn't designed to target the specific row where the input change happened.
Updated Code Solution
HTML
<table class="table table-bordered table-striped"> <thead> <th>#</th> <th>Item Name</th> <th>Batch No</th> <th>Remarks</th> <th>Quantity</th> <th>Cost Per Piece</th> <th>Total</th> <th>Action</th> </thead> <tbody id="tbody"> </tbody> <center><button type="button" onclick="addItem()" class="btn btn-success"> Add</button></center> </table>
JavaScript
// Initialize row counter properly let rowCount = 0; function addItem() { rowCount++; const row = document.getElementById("tbody").insertRow(); // Use classes instead of IDs for reusable elements row.innerHTML = ` <td><center>${rowCount}</center></td> <td><input type='text' class='form-control' name='product_name'></td> <td><input type='text' class='form-control' name='batch_no'></td> <td><input type='text' class='form-control' name='remarks'></td> <td><input type='number' class='form-control qty-input' name='total_qty'></td> <td><input type='number' class='form-control cost-input' name='cost'></td> <td><input type='number' class='form-control total-output' name='total' readonly></td> <td><button type='button' class='btn btn-danger delete-btn'><i class='fa fa-remove'></i></button></td> `; // Add event listeners to the new row's inputs const qtyInput = row.querySelector('.qty-input'); const costInput = row.querySelector('.cost-input'); const totalOutput = row.querySelector('.total-output'); function calculateRow() { const qty = parseFloat(qtyInput.value) || 0; const cost = parseFloat(costInput.value) || 0; totalOutput.value = qty * cost; } qtyInput.addEventListener('input', calculateRow); costInput.addEventListener('input', calculateRow); // Add delete functionality row.querySelector('.delete-btn').addEventListener('click', () => { row.remove(); // Optional: Re-number rows after deletion updateRowNumbers(); }); } // Optional: Update row numbers if a row is deleted function updateRowNumbers() { const rows = document.querySelectorAll('#tbody tr'); rows.forEach((row, index) => { row.querySelector('td:first-child center').textContent = index + 1; }); rowCount = rows.length; }
Key Improvements Explained
- Unique Classes Instead of Duplicate IDs: We use classes like
.qty-input,.cost-input, and.total-outputto identify elements within each row. This avoids HTML validation errors and lets us target elements per row. - Row-Specific Calculation: Instead of a global
calculate()function, we create acalculateRow()function tied directly to the new row's inputs. It only affects the row where the input change occurred. - Event Listeners Instead of Inline
oninput: Adding event listeners via JavaScript is cleaner and more maintainable than inline attributes. It also ensures each new row's inputs have their own calculation logic. - Proper Row Counter Initialization: We initialized
rowCountto 0 to avoid undefined variable errors. - Delete Functionality: Added a click listener to the delete button to remove the row, plus an optional function to re-number rows after deletion.
Alternative: Event Delegation (Even Cleaner!)
If you prefer to avoid adding event listeners every time you create a row, you can use event delegation on the table body. Here's how:
Replace the addItem() function's event listener code with this, and add this top-level listener:
// Add event delegation to the table body document.getElementById('tbody').addEventListener('input', (e) => { if (e.target.classList.contains('qty-input') || e.target.classList.contains('cost-input')) { const row = e.target.closest('tr'); const qty = parseFloat(row.querySelector('.qty-input').value) || 0; const cost = parseFloat(row.querySelector('.cost-input').value) || 0; row.querySelector('.total-output').value = qty * cost; } }); document.getElementById('tbody').addEventListener('click', (e) => { if (e.target.closest('.delete-btn')) { const row = e.target.closest('tr'); row.remove(); updateRowNumbers(); } }); function addItem() { rowCount++; const row = document.getElementById("tbody").insertRow(); row.innerHTML = ` <td><center>${rowCount}</center></td> <td><input type='text' class='form-control' name='product_name'></td> <td><input type='text' class='form-control' name='batch_no'></td> <td><input type='text' class='form-control' name='remarks'></td> <td><input type='number' class='form-control qty-input' name='total_qty'></td> <td><input type='number' class='form-control cost-input' name='cost'></td> <td><input type='number' class='form-control total-output' name='total' readonly></td> <td><button type='button' class='btn btn-danger delete-btn'><i class='fa fa-remove'></i></button></td> `; }
Event delegation works by listening for events on a parent element (the tbody), then checking if the event originated from a relevant child element. This way, you don't need to bind listeners to every new row—perfect for dynamic content!
内容的提问来源于stack exchange,提问作者ahad nadeem

