为多个事件配置相同的按钮 [英] configuring same button for multiple events

查看:112
本文介绍了为多个事件配置相同的按钮的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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



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




蓝色提交按钮被命名为addButton,当用户点击表格中特定行的编辑时,相应的数据显示在输入字段,因此用户可以填写新的详细信息。
到此为止一切正常,现在真正的问题开始了:用户在输入字段中编辑信息后,蓝色提交按钮用于保存这些更改并自动显示表中的更改。 (这是必需的行为)。



而不是发生的事情是:修改后的行显示在表格中,并且新的条目在行中生成,具有相同的细节( addButton事件监听器执行两次,一次用于保存用户完成的更改,一次用于在行中添加新项目)
我已经放置了此事件监听器(同名,bcz我不想要单独的btn用于保存已编辑的信息)两次。
为清楚起见,请查看代码:



(这是全局范围内的第一个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();
}
});

所以编辑后用户点击蓝色提交btn时,我只想要那个addButton事件监听器执行哪个是在modifyBtn事件监听器内,
目前这两个addButton事件监听器都在执行。 很抱歉给出了大量的解释。

解决方案

这个问题,你可能已经意识到,是你同时为同一个元素(你想要的行为)分配多个点击监听器(你不想要的行为)。有几种方法可以解决这个问题。






最快的修复



解决问题的最快方法是使用 .removeEventListener() 。这样,您可以在创建设置编辑数据的第二个单击侦听器之前删除先前的单击侦听器(将新元素添加到 info 阵列的侦听器)。 / p>

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



此解决方案的优点




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



此解决方案的缺点




  1. 解除绑定和重新绑定事件监听器会使其变得困难推理你的代码

  2. 为了删除事件监听器,你需要提供原始函数(比如 .removeEventListener(click,listenerFunction)。当前正在使用匿名函数表达式,你将拥有移动当前内部的函数点击侦听器别名并命名它们(这样你就可以将它们传递给 removeEventListener 函数

  3. 实际上并不是这样清除哪个事件监听器在任何时候都绑定到 addButton



解决方案



我们需要移动 addButton 的函数声明 of .addEventListener 并为其命名。我把它称为 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 所以我们可以在编辑完成后将其删除


  • 我们做了我们需要做的所有编辑


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







更强大的修复



以下是应用以下修复程序后应用程序的代码。



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



此解决方案的优点




  1. 我们不必解除绑定或重新绑定任何点击监听器

  2. 更容易推断出发生的事情



此解决方案的缺点




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



解决方案



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

首先,由于我们不是重新绑定点击听众,我们需要跟踪我们的内容重新编辑。让我们在对象下方创建一个名为编辑的对象:

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

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



步骤2:更新 addButton 事件监听器以使用编辑对象



接下来,我们需要修改我们的 addButton.addEventListener 来使用这个新的编辑 object:

  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.mode true ,当点击 addButton 时,它会更新值然后禁用 editing.mode ,将其恢复到以前的状态


  • 如果 editing.mode false ,它只是将项目添加到数组中(与之前的代码相同)


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




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



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

 < td>< a href =#data-id = {{id}} data-action =edit >编辑< / A> | < a href =#data-id = {{id}} data-action =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


  • 我移动了重复的代码在哪里检索 eid eindex 无论您正在执行什么操作('编辑'或'添加'),那些函数


  • 正如我们现在正在编辑的东西,我们将编辑对象设置为已启用:true 以及我们正在编辑的当前项目的索引是


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


  • 你不再需要保留你的 getIndex 函数,因为你可以使用 Array.prototype.findIndex() 而不是


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


  • 现在标准,无论发生什么,DOM都会刷新




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



好的,真棒!所以这完全有效。我要做的最后一件事是让它更清楚发生了什么,所以在< div class =下的 index.html 中form-modal> ,我要为你的 h2 添加一个ID:

 < h2 id =form-header>添加商品< / h2> 

然后,返回的顶部index.js

  formHeader = getElement('form-header'); 

最后在 refreshDOM()

  formHeader.textContent = editing.mode? 编辑项目:添加项目; 

这将更新内的文字< h2 id =form-header >< / h2> 取决于是否正在编辑。这是三元运营商,是通常用作根据布尔变量选择不同结果的快捷方式。






我希望这不是太多信息。我花了一些时间查看你的代码,并且真的想帮助我们做最好的实践!如果您有任何疑问,请告诉我们。


I have a form button in my web app, (purely made with Vanilla Js), The form is used to get data from user and when button is clicked, that data goes to a HTML table and so on.

To edit Table Row, i have placed Edit Button on the table column (at every row), image attached below would help to get clear idea :

The Blue Submit Button is Named as "addButton" , when the user clicks "Edit" of particular row in table, corresponding data is Shown In Input Field, So user can fill in new details. Upto here is everything fine, now the real problem begins: After user edits the info in Input fields, Blue Submit button is used for saving those changes and spontaneously show change in table as well. (this is the required behaviour).

Instead what is happening is : The modified row is shown in table as well as a new entry is made in row with same details ("addButton" event listener is executed twice, once for saving changes done by user, and once for adding new item in row) I have placed this event listener (same name, bcz i donot want separate btn for saving edited info) twice. For clarity, plz look at code :

(this is first addButton event listener in global scope - used for adding new entry to table)

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();
});

For "Edit and Delete" buttons in row, i have this : modifyBtn is tied to table, so i can get click target with that.

(Inside modifyBtn 2nd addButton event listener is present - used for saving changes and show output in table)

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();
    }
});

so after editing when user clicks blue submit btn , i want only that addButton event listener to execute which is inside modifyBtn event listener, Currently both addButton event listeners are getting executed. Sorry for Huge explanation.

解决方案

The issue, as you're likely aware, is that you're assigning multiple click listeners to the same element (a behaviour that you want) at the same time (a behaviour you don't want). There are a couple of ways you could fix this.


The fastest fix

The quickest way to solve your problem would be to use .removeEventListener(). With this, you can remove the previous click listener (the one that adds a new element to the info array) before you create the second click listener that sets the edited data.

At the end of the second click listener function, you would rebind the first click listener again so the behaviour returned to normal.

Pros of this solution

  1. It's the fastest way to solve the problem you're having

Cons of this solution

  1. Unbinding and rebinding event listeners can make it hard to reason about your code
  2. In order to remove an event listener, you need to supply the original function (like .removeEventListener("click", listenerFunction). As you are currently using an anonymous function expression, you'll have to move the functions currently inside click listeners elsewhere and name them (so that you can pass them to the removeEventListener function
  3. It's not actually clear which event listener is bound to addButton at any one time

The solution

We need to move the function declaration for addButton outside of .addEventListener and give it a name. I've called it addItemClickHandler but you can call it whatever you want:

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);

This should work exactly the same. Now we need to move the second event listener function you're trying to add into its own named function. As we're only referring to the name of the function from inside itself, we don't even need to move it out, just give it a name. I'm going to give it 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);
});

  • As you can see, we first remove the listener addItemClickHandler so that when you click on the "Add" button, it won't do anything

  • We then bind a different click listener, that we give the name editItemClickHandler so we can remove it after the edit is complete

  • We do all the edits we need to do

  • Finally, we remove the new edit click listener we created and re-add the original click listener, so the functionality goes back to normal


The more robust fix

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

The above solution is the fastest way to fix your problem, but there are more robust ways to ensure a working solution. In this solution, I'm going to tidy up some of your code in order to make it cleaner and easier to understand.

Pros of this solution

  1. We won't have to unbind or rebind any click listeners
  2. It's easier to reason about what's happening

Cons of this solution

  1. It'll take longer to implement, as it requires restructuring more of your code

The solution

Step 1: Keep track of whether we're editing or not

Firstly, as we're not rebinding click listeners, we need to keep track of what we're editing. Let's create an object called editing just below object:

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

This will let us keep track of whether or not we're editing anything (editing.mode), and what the index of the item we're editing is (editing.index).

Step 2: Update the addButton event listener to use the editing object

Next, we need to modify our addButton.addEventListener to use this new editing object:

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();
});

  • If editing.mode is true, when the addButton is clicked, it will update the values and then disable editing.mode, putting it back to the way it was before

  • If editing.mode is false, it will simply add the item to the array (same code as you had before)

  • No matter what happens, the DOM will get refreshed

Step 3: Update the modifyBtn event listener to use the editing object

Also, I've noticed you're using classes to modify programmatic behaviour instead of data- attributes. This is fine in a lot of cases, but for your exact use case of determining the behaviour, it's recommended to use data- attributes instead. We should also set the href's to #, as we don't need to use them as links:

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

Now, I've restructured your modifyBtn.addEventListener() to handle a these aspects differently:

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();
});

  • Using e.preventDefault() means that the browser wont navigate to the # href when clicking on the link

  • I've moved duplicate code where it retrieved eid and eindex no matter what action you were performing ('editing' or 'adding') outside of those functions

  • As we are now editing something, we set the editing object to have enabled: true and whatever the index of the current item we're editing is

  • Instead of using object.info[eindex] every time, I've assigned it to a variable

  • You no longer need to keep your getIndex function, as you can use Array.prototype.findIndex() instead

  • The recommended way to get data- attributes is to use element.dataset.name instead of element.getAttribute()

  • As is standard now, no matter what happens the DOM will get refreshed

Step 4: Add a dynamic form header

Alright, awesome! So this totally works. One final thing I'd like to do is make it more clear what is happening, so in your index.html under your <div class="form-modal">, I'm going to add an ID to your h2:

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

Then, back at the top of index.js:

formHeader = getElement('form-header');

And then finally in refreshDOM():

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

This will update the text inside <h2 id="form-header"></h2> to depending on whether or not it's being edited. This is a ternary operator, and is often used as a quick way to choose different outcomes depending on a boolean variable.


I hope this wasn't too much information. I've spent a while looking at your code and really wanted to help with best practices and such! Let me know if you have any questions!

这篇关于为多个事件配置相同的按钮的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆