You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JavaScript动态表格多行功能复用及新增行总价计算失效问题排查与解决

Fixing Calculation for Dynamic Table Rows + Correcting Duplicate IDs

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

  1. Unique Classes Instead of Duplicate IDs: We use classes like .qty-input, .cost-input, and .total-output to identify elements within each row. This avoids HTML validation errors and lets us target elements per row.
  2. Row-Specific Calculation: Instead of a global calculate() function, we create a calculateRow() function tied directly to the new row's inputs. It only affects the row where the input change occurred.
  3. 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.
  4. Proper Row Counter Initialization: We initialized rowCount to 0 to avoid undefined variable errors.
  5. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.30 04:47:43