如何清除按钮旧事件监听器,避免触发重复提交事件
问题描述
我有一个交易数据表,每行都配有一个Edit按钮,点击后可编辑数据并保存至数据库。当前遇到的问题:若依次点击多个行的Edit按钮,后续保存时所有被点击过的行都会使用最后一次编辑的数据。例如点击前5行的Edit按钮后,在最后点击的行中将数量修改为5,保存后所有此前点击过的行的数量都会变为5。
我曾尝试移除saveBtn和Edit按钮的事件监听器,但并未奏效。问题根源应该是事件监听器仍在后台运行——所有saveBtn和Edit按钮使用了相同的类名,点击一个Edit按钮后再点击另一个,会产生多个监听器实例。请问当用户点击不同的Edit按钮时,如何删除saveBtn的事件监听器?
相关代码
const csvToTable = (csv, transactionIdsAtIndex) => { let rows = csv.trim().split(/\r?\n|\r/); let table = ''; let tableRows = ''; let tableHeader = ''; rows.forEach((row, rowIndex) => { let tableColumns = ''; let columns = row.split(','); // split/separate the columns in a row if (rowIndex === 0) { columns.unshift(''); } else { columns.unshift('<button class="btn edit">Edit</button>'); } columns.forEach((column) => { tableColumns += rowIndex == 0 ? '<th>' + column + '</th>' : '<td>' + column + '</td>'; }); if (rowIndex == 0) { tableHeader += `<tr id="${rowIndex}">` + tableColumns + '</tr>'; } else { tableRows += `<tr id="${rowIndex}">` + tableColumns + '</tr>'; } }); table += '<table>'; table += '<thead>'; table += tableHeader; table += '</thead>'; table += '<tbody>'; table += tableRows; table += '</tbody>'; table += '</table>'; if (csvTable.classList.contains('hidden')) { csvTable.classList.remove('hidden'); } csvTable.innerHTML += table; downloadBtn.classList.remove('hidden'); handleEvents(transactionIdsAtIndex); } const handleEvents = (transactionIdsAtIndex) => { const editBtn = document.querySelectorAll('.edit'); editBtn.forEach(btn => btn.addEventListener('click', function editTransaction() { editTransactionForm(transactionIdsAtIndex[btn.parentElement.parentElement.id]); btn.removeEventListener('click', editTransaction); })); } const editTransactionForm = (id) => { console.log('editing') editForm.classList.remove('hidden'); downloadMenu.classList.add('hidden'); downloadBtn.classList.add('hidden'); api.call('Get', {typeName: 'FuelTransaction', search: {id: id}}, result => { const transaction = result[0]; volume.value = (transaction.volume / 3.785412534257983).toFixed(2); cost.value = transaction.cost.toFixed(2); pricePerUnit.value = (cost.value / volume.value).toFixed(2); fuelType.value = transaction.productType; saveBtn.addEventListener('click', function validateFormData () { console.log('saving'); let valid = true; if (isNaN(volume.value) || isNaN(cost.value) || isNaN(pricePerUnit.value)) { valid = false; popupText.textContent = 'Input must be a number'; popup.classList.remove('hidden'); } else if (!volume.value || !cost.value || !pricePerUnit.value) { valid = false; popupText.textContent = 'Missing input'; popup.classList.remove('hidden'); } else if (fuelType.value.length === 0) { valid = false; popupText.textContent = 'Must enter a fuel type'; popup.classList.remove('hidden'); } if (valid) { transaction.volume = Number(volume.value * 3.785412534257983); transaction.cost = Number(cost.value); transaction.productType = String(fuelType.value); popupText.textContent = 'Saving...'; popup.classList.remove('hidden'); api.call('Set', {typeName: 'FuelTransaction', entity: transaction}, () => { saveBtn.removeEventListener('click', validateFormData); popup.classList.add('hidden'); editForm.classList.add('hidden'); downloadMenu.classList.remove('hidden'); getTransactionDetails(); }, err => { popupText.textContent = 'Error saving transaction'; popup.classList.remove('hidden'); console.log(err) }) } }) }) }
解决方案
方法一:用全局变量跟踪并移除旧监听器
每次打开新的编辑表单前,先移除saveBtn上绑定的旧监听器,确保只有当前编辑对应的监听器生效:
// 全局变量保存当前的保存监听器 let currentSaveListener = null; const editTransactionForm = (id) => { // 移除之前绑定的旧监听器 if (currentSaveListener) { saveBtn.removeEventListener('click', currentSaveListener); currentSaveListener = null; } console.log('editing') editForm.classList.remove('hidden'); downloadMenu.classList.add('hidden'); downloadBtn.classList.add('hidden'); api.call('Get', {typeName: 'FuelTransaction', search: {id: id}}, result => { const transaction = result[0]; volume.value = (transaction.volume / 3.785412534257983).toFixed(2); cost.value = transaction.cost.toFixed(2); pricePerUnit.value = (cost.value / volume.value).toFixed(2); fuelType.value = transaction.productType; // 保存当前监听器到全局变量 currentSaveListener = function validateFormData () { console.log('saving'); let valid = true; if (isNaN(volume.value) || isNaN(cost.value) || isNaN(pricePerUnit.value)) { valid = false; popupText.textContent = 'Input must be a number'; popup.classList.remove('hidden'); } else if (!volume.value || !cost.value || !pricePerUnit.value) { valid = false; popupText.textContent = 'Missing input'; popup.classList.remove('hidden'); } else if (fuelType.value.length === 0) { valid = false; popupText.textContent = 'Must enter a fuel type'; popup.classList.remove('hidden'); } if (valid) { transaction.volume = Number(volume.value * 3.785412534257983); transaction.cost = Number(cost.value); transaction.productType = String(fuelType.value); popupText.textContent = 'Saving...'; popup.classList.remove('hidden'); api.call('Set', {typeName: 'FuelTransaction', entity: transaction}, () => { saveBtn.removeEventListener('click', currentSaveListener); currentSaveListener = null; popup.classList.add('hidden'); editForm.classList.add('hidden'); downloadMenu.classList.remove('hidden'); getTransactionDetails(); }, err => { popupText.textContent = 'Error saving transaction'; popup.classList.remove('hidden'); console.log(err) }) } } saveBtn.addEventListener('click', currentSaveListener); }) }
方法二:只绑定一次保存监听器,跟踪当前编辑实例
初始化时给saveBtn只绑定一次监听器,用全局变量记录当前正在编辑的交易,避免多次绑定监听器:
// 全局变量记录当前正在编辑的交易 let currentEditingTransaction = null; // 页面初始化时绑定一次保存监听器 saveBtn.addEventListener('click', function validateFormData () { if (!currentEditingTransaction) return; console.log('saving'); let valid = true; if (isNaN(volume.value) || isNaN(cost.value) || isNaN(pricePerUnit.value)) { valid = false; popupText.textContent = 'Input must be a number'; popup.classList.remove('hidden'); } else if (!volume.value || !cost.value || !pricePerUnit.value) { valid = false; popupText.textContent = 'Missing input'; popup.classList.remove('hidden'); } else if (fuelType.value.length === 0) { valid = false; popupText.textContent = 'Must enter a fuel type'; popup.classList.remove('hidden'); } if (valid) { currentEditingTransaction.volume = Number(volume.value * 3.785412534257983); currentEditingTransaction.cost = Number(cost.value); currentEditingTransaction.productType = String(fuelType.value); popupText.textContent = 'Saving...'; popup.classList.remove('hidden'); api.call('Set', {typeName: 'FuelTransaction', entity: currentEditingTransaction}, () => { popup.classList.add('hidden'); editForm.classList.add('hidden'); downloadMenu.classList.remove('hidden'); currentEditingTransaction = null; getTransactionDetails(); }, err => { popupText.textContent = 'Error saving transaction'; popup.classList.remove('hidden'); console.log(err) }) } }); const editTransactionForm = (id) => { console.log('editing') editForm.classList.remove('hidden'); downloadMenu.classList.add('hidden'); downloadBtn.classList.add('hidden'); api.call('Get', {typeName: 'FuelTransaction', search: {id: id}}, result => { // 更新当前编辑的交易实例 currentEditingTransaction = result[0]; volume.value = (currentEditingTransaction.volume / 3.785412534257983).toFixed(2); cost.value = currentEditingTransaction.cost.toFixed(2); pricePerUnit.value = (cost.value / volume.value).toFixed(2); fuelType.value = currentEditingTransaction.productType; }) }
方法三:克隆按钮清除所有监听器
每次打开编辑表单时,克隆saveBtn替换原元素,克隆后的元素会自动清除所有绑定的事件监听器:
const editTransactionForm = (id) => { // 克隆saveBtn替换原元素,清除所有旧监听器 const newSaveBtn = saveBtn.cloneNode(true); saveBtn.parentNode.replaceChild(newSaveBtn, saveBtn); saveBtn = newSaveBtn; console.log('editing') editForm.classList.remove('hidden'); downloadMenu.classList.add('hidden'); downloadBtn.classList.add('hidden'); api.call('Get', {typeName: 'FuelTransaction', search: {id: id}}, result => { const transaction = result[0]; volume.value = (transaction.volume / 3.785412534257983).toFixed(2); cost.value = transaction.cost.toFixed(2); pricePerUnit.value = (cost.value / volume.value).toFixed(2); fuelType.value = transaction.productType; saveBtn.addEventListener('click', function validateFormData () { // ... 原有的验证和保存逻辑 }) }) }
内容的提问来源于stack exchange,提问作者mrplayingame
相关产品推荐
相关产品推荐

