ASP.NET 6 MVC中用JavaScript循环表格文本框ID的问题求助
问题分析与解决方案
问题根源
循环生成表格行时,手动设置了重复的固定ID(如lstProduct、txtPrice),导致JavaScript通过document.getElementById只会选中页面中第一个匹配的元素,新增行后操作仍修改第一行数据。
修复步骤
1. 修改HTML代码:移除重复ID,使用类名定位
去掉手动设置的id属性,保留asp-for让ASP.NET自动生成唯一ID,同时为需要操作的元素添加专属类名:
<table class="table table-hover MyTable table-bordered" id="MyTable"> <thead> <tr> <th style="width:19%;">Product Code</th> <th style="width:24%;">Product Name</th> <th style="width:15%;">Quantity</th> <th style="width:15%;">Price</th> <th style="width:15%;">Amount</th> <th style="width:12%;"> <button id='btnAddDetailRow' type="button" class="btn btn-sm btn-secondary visible" onclick="AddItem()"> Add </button> </th> </tr> </thead> <tbody> @for (int i = 0; i < Model.InvoiceDetails.Count; i++) { <tr> <td style="width:19%;"> <select asp-for="@Model.InvoiceDetails[i].ProductId" class="form-control Product" asp-items="@ViewBag.Products" onchange="handleProductChange(this);"></select> <span asp-validation-for="@Model.InvoiceDetails[i].ProductId" class="text-danger"></span> </td> <td style="width:24%;"> <input asp-for="@Model.InvoiceDetails[i].Product" class="form-control form-control-plaintext product-name" readonly /> <span asp-validation-for="@Model.InvoiceDetails[i].Product" class="text-danger"></span> </td> <td style="width:15%;"> <input asp-for="@Model.InvoiceDetails[i].Quantity" class="form-control QtyTotal" /> <span asp-validation-for="@Model.InvoiceDetails[i].Quantity" class="text-danger"></span> </td> <td style="width:15%;"> <input asp-for="@Model.InvoiceDetails[i].Price" class="form-control form-control-plaintext PriceTotal product-price" readonly /> <span asp-validation-for="@Model.InvoiceDetails[i].Price" class="text-danger"></span> </td> <td style="width:15%;"> <input type="hidden" asp-for="@Model.InvoiceDetails[i].IsDeleted" /> <input asp-for="@Model.InvoiceDetails[i].Amount" class="form-control form-control-plaintext AmountTotal" readonly /> <span asp-validation-for="@Model.InvoiceDetails[i].Amount" class="text-danger"></span> </td> <td style="width:12%;"> <button id='btnremove-@i' type="button" class="btn btn-sm btn-danger visible" onclick="DeleteItem(this)"> Delete </button> </td> </tr> } </tbody> </table>
2. 重构JavaScript函数:基于当前行定位元素
合并原有分散的函数,通过触发事件的元素找到所在行,精准定位该行的输入框:
<script type="text/javascript"> function handleProductChange(selectElement) { // 获取当前操作的表格行 const currentRow = selectElement.closest('tr'); // 定位该行内的目标输入框 const productNameInput = currentRow.querySelector('.product-name'); const productPriceInput = currentRow.querySelector('.product-price'); const amountInput = currentRow.querySelector('.AmountTotal'); const selectedProductId = selectElement.value; // 从全局下拉框获取产品名称 const productNameDropdown = document.getElementById('dropdownProductName'); const matchedName = Array.from(productNameDropdown.options).find(opt => opt.value === selectedProductId)?.text || ''; productNameInput.value = matchedName; // 从全局下拉框获取产品价格 const productPriceDropdown = document.getElementById('dropdownPrice'); const matchedPrice = Array.from(productPriceDropdown.options).find(opt => opt.value === selectedProductId)?.text || ''; productPriceInput.value = matchedPrice; // 计算金额 calculateRowAmount(currentRow); } // 计算单行金额 function calculateRowAmount(row) { const quantity = parseFloat(row.querySelector('.QtyTotal').value) || 0; const price = parseFloat(row.querySelector('.product-price').value) || 0; row.querySelector('.AmountTotal').value = (quantity * price).toFixed(2); } // 监听数量输入变化,自动重新计算金额 document.addEventListener('input', function(e) { if (e.target.classList.contains('QtyTotal')) { calculateRowAmount(e.target.closest('tr')); } }); function AddItem() { const table = document.getElementById('MyTable'); const rows = table.getElementsByTagName('tr'); const lastRow = rows[rows.length - 1]; let rowOuterHtml = lastRow.outerHTML; const lastrowIdx = rows.length - 2; const nextrowIdx = lastrowIdx + 1; // 替换索引,确保新行的绑定属性正确 rowOuterHtml = rowOuterHtml.replaceAll(`_${lastrowIdx}_`, `_${nextrowIdx}_`); rowOuterHtml = rowOuterHtml.replaceAll(`[${lastrowIdx}]`, `[${nextrowIdx}]`); rowOuterHtml = rowOuterHtml.replaceAll(`-${lastrowIdx}`, `-${nextrowIdx}`); const newRow = table.insertRow(); newRow.innerHTML = rowOuterHtml; // 清空新行的输入值 const inputs = newRow.querySelectorAll('input'); inputs.forEach(input => { if (input.classList.contains('product-name') || input.classList.contains('product-price') || input.classList.contains('AmountTotal')) { input.value = ''; } else if (input.type === 'number') { input.value = 0; } }); } function DeleteItem(btn) { btn.closest('tr').remove(); } </script>
关键优化点
- 消除ID冲突:移除手动固定ID,依赖ASP.NET自动生成的唯一ID,避免DOM选择错误。
- 行内精准定位:通过
closest('tr')锁定当前操作行,再用类名查找元素,确保操作目标是当前行的输入框。 - 逻辑合并简化:将多个分散的函数整合为一个处理逻辑,减少冗余代码。
- 自动计算金额:监听数量输入变化,实时更新金额,提升交互体验。
可选优化:直接通过Select选项携带数据
如果产品数据来自后端,可以将名称和价格存储在Select选项的data-*属性中,无需依赖全局下拉框:
<select asp-for="@Model.InvoiceDetails[i].ProductId" class="form-control Product" asp-items="@(ViewBag.Products.Select(p => new SelectListItem { Value = p.Id.ToString(), Text = p.Name, Data = new { price = p.Price.ToString() } }))" onchange="handleProductChange(this);"></select>
然后修改JavaScript函数:
function handleProductChange(selectElement) { const currentRow = selectElement.closest('tr'); const selectedOption = selectElement.options[selectElement.selectedIndex]; currentRow.querySelector('.product-name').value = selectedOption.text; currentRow.querySelector('.product-price').value = selectedOption.dataset.price; calculateRowAmount(currentRow); }
内容的提问来源于stack exchange,提问作者Ramadhan Kazyoba
相关产品推荐
相关产品推荐

