I would like to attach an event listener (that calls a function) to a button that is not yet present in the DOM. I don't want to use inline-call in the HTML how can I do it?
So far i could achieve what I wanted by creating a global event listener
that checks if the element clicked has a specific Id. But I found this solution to be dirty and not optimal.
const messageForm = document.querySelector("#message-form")
const messageTextarea = document.querySelector("#message-textarea");
const messageList = document.querySelector("#message-list");
const messageEmpty = document.querySelector("#message-empty");
let messageNumber = 0;
const messageFormatted = messageText => {
return `
<li class="message-item">
<img class="message-avatar" src="./icons/boy.png" alt="Username">
<article class="message-content">
<p class="author">Ernesto Campese</p>
<p class="message">${messageText}</p>
<p class="datetime">A moment ago</p>
</article>
<div class="message-actions">
<img id="message-edit" class="action-button" src="./icons/edit.png" alt="" width="22" height="22">
<img id="message-delete" class="action-button" src="./icons/delete.png" alt="" width="22" height="22">
</div>
</li>
`;
}
document.addEventListener("click", (e) => {
if (e.target.id === "message-delete") {
e.target.parentNode.parentNode.remove();
messageNumber--;
messageEmptyCheck();
}
})
messageForm.addEventListener("submit", (e) => {
e.preventDefault();
if (messageTextarea.value !== "") {
messageList.insertAdjacentHTML("beforeend", messageFormatted(messageTextarea.value));
messageTextarea.value = null;
messageTextarea.focus();
messageNumber++;
messageEmptyCheck();
}
});
As you can see in the code, inside the <li>
that I'm creating there are two IMG, one is for deleting and the other for editing. I want to add an event listener to the delete IMG, so when the user clicks it, the li gets eliminated.
The problem is that I cannot create any function if the element does not exist yet. I would love to do something like:
const messageDeleteButton = document.querySelector("#message-delete");
messageDeleteButton.addEventListener("click", (e) => {
e.parentNode.parentNode.remove();
}
})
Hope I was clear enough, thank you guys!
I would like to attach an event listener (that calls a function) to a button that is not yet present in the DOM. I don't want to use inline-call in the HTML how can I do it?
So far i could achieve what I wanted by creating a global event listener
that checks if the element clicked has a specific Id. But I found this solution to be dirty and not optimal.
const messageForm = document.querySelector("#message-form")
const messageTextarea = document.querySelector("#message-textarea");
const messageList = document.querySelector("#message-list");
const messageEmpty = document.querySelector("#message-empty");
let messageNumber = 0;
const messageFormatted = messageText => {
return `
<li class="message-item">
<img class="message-avatar" src="./icons/boy.png" alt="Username">
<article class="message-content">
<p class="author">Ernesto Campese</p>
<p class="message">${messageText}</p>
<p class="datetime">A moment ago</p>
</article>
<div class="message-actions">
<img id="message-edit" class="action-button" src="./icons/edit.png" alt="" width="22" height="22">
<img id="message-delete" class="action-button" src="./icons/delete.png" alt="" width="22" height="22">
</div>
</li>
`;
}
document.addEventListener("click", (e) => {
if (e.target.id === "message-delete") {
e.target.parentNode.parentNode.remove();
messageNumber--;
messageEmptyCheck();
}
})
messageForm.addEventListener("submit", (e) => {
e.preventDefault();
if (messageTextarea.value !== "") {
messageList.insertAdjacentHTML("beforeend", messageFormatted(messageTextarea.value));
messageTextarea.value = null;
messageTextarea.focus();
messageNumber++;
messageEmptyCheck();
}
});
As you can see in the code, inside the <li>
that I'm creating there are two IMG, one is for deleting and the other for editing. I want to add an event listener to the delete IMG, so when the user clicks it, the li gets eliminated.
The problem is that I cannot create any function if the element does not exist yet. I would love to do something like:
const messageDeleteButton = document.querySelector("#message-delete");
messageDeleteButton.addEventListener("click", (e) => {
e.parentNode.parentNode.remove();
}
})
Hope I was clear enough, thank you guys!
Share Improve this question edited Sep 1, 2019 at 20:03 Salim Djerbouh 11.1k6 gold badges33 silver badges64 bronze badges asked Sep 1, 2019 at 17:21 RavenJeGamesRavenJeGames 1532 silver badges10 bronze badges 2- Attach the listeners to the document level rather than the element level – Dev Man Commented Sep 1, 2019 at 17:26
- @ImmortalDude - See the code in the question. – T.J. Crowder Commented Sep 1, 2019 at 17:26
3 Answers
Reset to default 3You could parse your HTML string to a document
in JS and then add the event listener to that element.
So you've got your Template Literal string.
const messageFormatted = messageText => {
return `
<li class="message-item">
<img class="message-avatar" src="./icons/boy.png" alt="Username">
<article class="message-content">
<p class="author">Ernesto Campese</p>
<p class="message">${messageText}</p>
<p class="datetime">A moment ago</p>
</article>
<div class="message-actions">
<img id="message-edit" class="action-button" src="./icons/edit.png" alt="" width="22" height="22">
<img id="message-delete" class="action-button" src="./icons/delete.png" alt="" width="22" height="22">
</div>
</li>
`;
}
You can transform your string
into HTML by writing a function like the one below.
It uses the DOMParser API
which creates a new document
that will contain all of your HTML you've written in your string
.
const parseStringToHTML = (str) => {
const parser = new DOMParser();
return parser.parseFromString(str, 'text/html');
};
And this document works like the one you have in your page, its just a new one that only exists in your JS memory for the time being. Give your string
as argument to parseStringToHTML
to create HTML.
const message = messageFormatted('This is the message'); // Example message
const messageHTML = parseStringToHTML(message); // Returns a document object with all the features a document object has.
So now that your string
is a document
you can use methods on it like getElementById
, querySelector
, etc. But first you must select the element that you need.
const messageDeleteButton = messageHTML.querySelector("#message-delete");
See that I've used messageHTML
instead of document
. In this scenario messageHTML
is a document
and therefor we can query inside of it. And now you've found your element inside this document
you can add an event listener to it.
messageDeleteButton.addEventListener("click", (e) => {
e.parentNode.parentNode.remove();
});
Okay, so now the event listener has been added. All you have to do now is append the HTML you need from the messageHTML
into the document
of your page. The HTML can be found in the messageHTML.body
property.
Now instead of insertAdjacentHTML
use insertAdjacentElement
to insert the element in the position of your choosing. The element you want to append is the <li class="message-item">
which would be the firstElementChild
in the body
of the messageHTML.body
property.
So in your submit
event listener of your form change the following line from:
messageList.insertAdjacentHTML("beforeend", messageFormatted(messageTextarea.value));
To all that I've explained above.
// Create string
const message = messageFormatted(messageTextarea.value);
// String to document
const messageHTML = parseStringToHTML(message);
// Select delete element.
const messageDeleteButton = messageHTML.querySelector("#message-delete");
// Add event listener to delete element.
messageDeleteButton.addEventListener("click", (e) => {
e.parentNode.parentNode.remove();
});
// Append the <li> element to the messageList.
messageList.insertAdjacentElement("beforeend", messageHTML.body.firstElementChild);
Don't forget to include the parseStringToHTML
function in your code. I understand that this is a lot to work out so if you have any question please do ask.
I hope that this will help you out.
Note I see that event delegation (global event listener) is not something you want to do, although as other have stated, it is more performant and easier to implement than other methods, even my own answer. It would also solve listening for elements that you've added or even removed from your list. You could set it even on the <ul id="message-list">
element like T.J. Crowder suggested.
You're on the right track. The identifying characteristic doesn't have to be an id
, it can be anything about the element — a class is a mon approach.
There are three DOM methods that will help with making the implementation flexible:
closest
- starting with the element you call it on, it finds the nearest ancestor matching a given CSS selectorcontains
- checks to see if a node contains another nodematches
- checks to see if the element you call it on matches a given CSS selector
For instance, your current handler requires that the button be the element that was clicked (event.target
). That's fine for your img
use case, but what if it were a button
with an img
inside it? Then event.target
might be the button
of the img
, depending on where the user clicked. closest
and contains
help with that:
document.addEventListener("click", (e) => {
const button = e.target.closest("#message-delete");
if (button) {
button.closest("li").remove();
messageNumber--;
messageEmptyCheck();
}
})
You'll also want to hook the events on the nearest container that will work. Sometimes, you're stuck with it being document
or document.body
. But oftentimes, you can do better than that. For instance, when listening for button clicks on buttons that are in the cells of a table, you can listen at the table
or tbody
/thead
level rather than document. That's where contains
es into the equation, it makes sure closest
didn't go too far up the document tree:
document.querySelector("selector-for-the-table").addEventListener("click", (e) => {
const button = e.target.closest("#message-delete");
if (button && e.currentTarget.contains(button)) {
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
button.closest("li").remove();
messageNumber--;
messageEmptyCheck();
}
})
It's not likely in your case that the search for #message-delete
will have gone out of the containing element, so you may not need that bit, but sometimes it can happen and so you want that contains
check.
If you really want to add event listener to that element itself then I think you may want to try MutationObserver - https://developer.mozilla/en-US/docs/Web/API/MutationObserver
You can observe the events with passing config for childList, subtree and on the event you can check whether the element you are looking for has been added or it is there. If you find that you can add the event listener.
You may want to look at following articles - https://eager.io/blog/three-real-world-use-cases-for-mutation-observer/ https://blog.sessionstack./how-javascript-works-tracking-changes-in-the-dom-using-mutationobserver-86adc7446401
I hope it would be helpful.
发布者:admin,转转请注明出处:http://www.yc00.com/questions/1744194773a4562603.html
评论列表(0条)