【问题标题】:Configuring same button for multiple events为多个事件配置相同的按钮
【发布时间】:2018-07-17 06:40:05
【问题描述】:

我的网络应用程序中有一个表单按钮,(纯粹用 Vanilla Js 制作),该表单用于从用户那里获取数据,当单击按钮时,该数据会转到 HTML 表格等等。

要编辑表格行,我在表格列(每一行)上放置了编辑按钮,下面附上的图片将有助于清楚地了解:

蓝色的提交按钮被命名为“addButton”,当用户点击表格中特定行的“编辑”时,相应的数据显示在输入字段中,因此用户可以填写新的详细信息。

到这里一切都很好,现在真正的问题开始了:在用户编辑输入字段中的信息后,蓝色提交按钮用于保存这些更改并自发地在表格中显示更改。 (这是必需的行为)。

实际上发生的是:修改后的行显示在表中,并且在行中创建了具有相同详细信息的新条目(“addButton”事件侦听器执行两次,一次用于保存用户所做的更改,一次用于在行中添加新项目)。

我已经放置了这个事件监听器(同名,因为我不想要单独的按钮来保存编辑的信息)两次。 为清楚起见,请查看代码:

(这是全局范围内的第一个 addButton 事件监听器 - 用于向表中添加新条目)

addButton.addEventListener("click", function(e){
    var obj = {
        id : object.count,
        date : formatDate(inDate.value, inMonth.value),
        day : inDay.value,
        item : inItem.value,
        price : parseInt(inPrice.value),
    };
    object.info.push(obj);
    object.count += 1;
    refreshDOM();
});

对于行中的“编辑和删除”按钮,我有这个: modifyBtn 绑定到表格,所以我可以通过它获得点击目标。

(在 modifyBtn 内部存在第二个 addButton 事件侦听器 - 用于保存更改并在表格中显示输出)

modifyBtn.addEventListener('click', function(e){
    if(e.target.classList[0] === 'editInfo'){
        var eid = e.target.getAttribute('data-id');
        var eindex = getIndex(eid);
        
        inDay.value = object.info[eindex]['day'];
        inDate.value = parseInt(object.info[eindex]['date'].substr(0,2));
        inMonth.value = object.info[eindex]["date"].substr(4);
        
        inItem.value = object.info[eindex]['item'];
        inPrice.value = object.info[eindex]['price'];
        addButton.addEventListener("click", function(e){
            object.info[eindex]['day'] = inDay.value;
            object.info[eindex]['date'] = formatDate(inDate.value, inMonth.value);
            object.info[eindex]['item'] = inItem.value;
            object.info[eindex]['price'] = parseInt(inPrice.value);
            refreshDOM();
        });
    }
    if(e.target.classList[0] === 'deleteInfo'){
        var did = e.target.getAttribute('data-id');
        var dindex = getIndex(did);
        console.log(dindex);
        object.info.splice(dindex,1);
        refreshDOM();
    }
});

所以在用户点击蓝色提交按钮进行编辑后,我只希望在 modifyBtn 事件监听器中执行 addButton 事件监听器, 目前两个addButton 事件监听器都被执行。 很抱歉,您的解释太深了。

【问题讨论】:

  • 嘿克鲁佩什!我有一个有效的修复,但它很长,所以请耐心等待我写它:-)
  • 当然,为什么不呢。急切地等待解决方案。顺便说一句,您是否尝试过使用该应用程序,将一些数据输入到表中然后对其进行编辑。你注意到我上面提到的错误了吗?

标签: javascript dom-events


【解决方案1】:

您可能知道,问题在于您同时将多个点击侦听器分配给同一个元素(您想要的行为)(您不想要的行为)。有几种方法可以解决此问题。


最快的修复

解决问题的最快方法是使用.removeEventListener()。有了这个,您可以在创建设置编辑数据的第二个点击侦听器之前删除前一个点击侦听器(向info 数组添加新元素的侦听器)。

在第二个点击监听函数结束时,您将再次重新绑定第一个点击监听,以使行为恢复正常。

此解决方案的优点

  1. 这是解决您遇到的问题的最快方法

此解决方案的缺点

  1. 取消绑定和重新绑定事件侦听器会使您的代码难以推理
  2. 要删除事件监听器,您需要提供原始函数(如.removeEventListener("click", listenerFunction)。由于您当前使用的是anonymous function expression,因此您必须将当前位于点击监听器中的函数移到别处并命名它们(这样您就可以将它们传递给removeEventListener 函数
  3. 实际上并不清楚哪个事件侦听器在任何时候都绑定到addButton

解决方案

我们需要将addButton 的函数声明移到.addEventListener outside 并给它一个名字。我将其命名为 addItemClickHandler,但您可以随意命名:

function addItemClickHandler(e) {
    var obj = {
        id : object.count,
        date : formatDate(inDate.value, inMonth.value),
        day : inDay.value,
        item : inItem.value,
        price : parseInt(inPrice.value),
    };
    object.info.push(obj);
    object.count += 1;
    refreshDOM();
}

addButton.addEventListener("click", addItemClickHandler);

这应该完全一样。现在我们需要将您尝试添加的第二个事件侦听器函数移动到它自己的命名函数中。由于我们只是从内部引用函数的名称,因此我们甚至不需要将其移出,只需给它一个名称即可。我要给它editItemClickHandler

addButton.removeEventListener("click", addItemClickHandler);
addButton.addEventListener("click", function editItemClickHandler(e){
    object.info[eindex]['day'] = inDay.value;
    object.info[eindex]['date'] = formatDate(inDate.value, inMonth.value);
    object.info[eindex]['item'] = inItem.value;
    object.info[eindex]['price'] = parseInt(inPrice.value);
    refreshDOM();

    addButton.removeEventListener("click", editItemClickHandler);
    addButton.addEventListener("click", addItemClickHandler);
});
  • 如您所见,我们首先删除了监听器addItemClickHandler,这样当您点击“添加”按钮时,它不会做任何事情

  • 然后我们绑定一个不同的点击监听器,我们将其命名为editItemClickHandler,这样我们就可以在编辑完成后移除它

  • 我们做了所有需要做的修改

  • 最后,我们删除我们创建的新的编辑点击监听器并重新添加原来的点击监听器,所以功能恢复正常


更强大的修复

Here is a codepen of your application after applying the following fixes.

上述解决方案是解决问题的最快方法,但还有更强大的方法可以确保解决方案有效。在这个解决方案中,我将整理您的一些代码,使其更清晰、更易于理解。

此解决方案的优点

  1. 我们不必取消绑定或重新绑定任何点击侦听器
  2. 更容易推断正在发生的事情

此解决方案的缺点

  1. 实施需要更长的时间,因为它需要重组更多代码

解决方案

第 1 步:跟踪我们是否正在编辑

首先,由于我们不会重新绑定点击侦听器,因此我们需要跟踪我们正在编辑的内容。让我们在object 下方创建一个名为editing 的对象:

var editing = {
    mode: false,
    index: null
};

这将让我们跟踪我们是否正在编辑任何内容 (editing.mode),以及我们正在编辑的项目的索引是什么 (editing.index)。

第 2 步:更新 addButton 事件侦听器以使用 editing 对象

接下来,我们需要修改我们的addButton.addEventListener 以使用这个新的editing 对象:

addButton.addEventListener("click", function(e){
    if (editing.mode) {
        var info = object.info[editing.index];

        info['day'] = inDay.value;
        info['date'] = formatDate(inDate.value, inMonth.value);
        info['item'] = inItem.value;
        info['price'] = parseInt(inPrice.value);

        editing.mode = false;
    } else {
        var obj = {
            id : object.count,
            date : formatDate(inDate.value, inMonth.value),
            day : inDay.value,
            item : inItem.value,
            price : parseInt(inPrice.value),
        };
        object.info.push(obj);
        object.count += 1;
    }

    refreshDOM();
});
  • 如果editing.modetrue,当addButton被点击时,它会更新值然后禁用editing.mode,恢复到原来的样子

    李>
  • 如果 editing.modefalse,它只会将项目添加到数组中(与之前的代码相同)

  • 无论发生什么,DOM 都会刷新

第 3 步:更新 modifyBtn 事件侦听器以使用 editing 对象

另外,我注意到您正在使用类来修改编程行为,而不是 data- 属性。这在很多情况下都很好,但对于确定行为的确切用例,建议改用 data- 属性。我们还应该将href 设置为#,因为我们不需要将它们用作链接:

<td><a href="#" data-id={{id}} data-action="edit">Edit</a> | <a href="#" data-id={{id}} data-action="delete">Delete</a></td>

现在,我对您的modifyBtn.addEventListener() 进行了重组,以不同方式处理这些方面:

modifyBtn.addEventListener('click', function(e){
    e.preventDefault();

    var el = e.target;
    var id = parseInt(el.dataset.id);
    var index = object.info.findIndex(item => item.id === id);
    var info = object.info[index];
    var action = el.dataset.action;

    if (action === "edit") {
        editing.mode = true;
        editing.index = index;

        inDay.value = info['day'];
        inDate.value = parseInt(info['date'].substr(0,2));
        inMonth.value = info["date"].substr(4);
        inItem.value = info['item'];
        inPrice.value = info['price'];
    }

    if (action === "delete") {
        object.info.splice(index, 1);
    }

    refreshDOM();
});
  • 使用e.preventDefault()意味着当点击链接时浏览器不会导航到# href

  • 无论您在这些函数之外执行什么操作(“编辑”或“添加”),我已将重复代码移到它检索到 eideindex 的位置

  • 当我们现在正在编辑某些内容时,我们将 editing 对象设置为具有 enabled: true 以及我们正在编辑的当前项目的索引是什么

  • 我没有每次都使用object.info[eindex],而是将它分配给一个变量

  • 您不再需要保留 getIndex 函数,因为您可以改用 Array.prototype.findIndex()

  • 获取data-属性的推荐方法是使用element.dataset.name而不是element.getAttribute()

  • 按照现在的标准,无论发生什么情况,DOM 都会刷新

第 4 步:添加动态表单标题

好的,太棒了!所以这完全有效。我想做的最后一件事是更清楚地说明发生了什么,所以在您的&lt;div class="form-modal"&gt; 下的index.html 中,我将向您的h2 添加一个ID:

<h2 id="form-header">Add Item</h2>

然后,回到index.js的顶部:

formHeader = getElement('form-header');

最后在refreshDOM():

formHeader.textContent = editing.mode ? "Edit Item" : "Add Item";

这会将&lt;h2 id="form-header"&gt;&lt;/h2&gt; 中的文本更新为取决于它是否正在被编辑。这是ternary operator,通常用作根据布尔变量选择不同结果的快速方法。


我希望这不是太多的信息。我花了一段时间查看您的代码,真的很想帮助您提供最佳实践等!如果您有任何问题,请告诉我!

【讨论】:

  • 阅读您的答案,将在实施后通知您。顺便找到了类似的Stack溢出线程:stackoverflow.com/questions/1115000/…
  • 检查并理解了您的全部答案。事实证明这很有帮助,我觉得我在 stackoverflow 中创建帐户就像是一种祝福。像你这样的人给出的答案很好。我很欣赏你的解释方式,一切都以简单的步骤完成。一定花费了您宝贵的时间来了解我的应用程序并删除错误。非常感谢。我是一名大学生,想和人一起建造东西。我是这个工作领域的初学者,坦率地说,以前从未与任何人合作过。你会认为我是兼职和你一起工作,创造伟大的东西吗!
  • 小小帮助,你能否在 cmets 中用普通的旧 javascript 重写这一行:index = object.info.findIndex(item => item.id === id);
  • 很高兴能帮上忙,不客气!那是普通的旧 javascript :) 如果你不想使用它,你最好的选择可能只是放回你的 getIndex() 函数,因为这就是它的全部!
  • 我刚刚完全阅读了您的信息,并坚持下去!我也是大学生,所以恐怕我没有资格雇用你?熟能生巧!你建造的越多,你得到的就越好。
猜你喜欢
  • 2016-05-29
  • 1970-01-01
  • 2012-07-31
  • 2021-10-23
  • 1970-01-01
  • 2013-01-11
  • 1970-01-01
  • 1970-01-01
  • 2014-07-13
相关资源
最近更新 更多