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.
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 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.
.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
functionaddButton
at any one timeWe 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
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.
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
).
addButton
event listener to use the editing
objectNext, 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
modifyBtn
event listener to use the editing
objectAlso, 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
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!