如何在尚未出现在 DOM 中的元素上添加事件监听器 [no-jQuery]?
我想将事件监听器(调用函数)附加到 DOM 中尚不存在的按钮。我不想在 HTML 中使用内联调用,我该怎么做?
到目前为止,我可以通过创建一个
全局事件监听器
来实现我想要的,该监听器检查单击的元素是否具有特定的 ID。但我发现这个解决方案很肮脏,不是最佳的。
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();
}
});
正如您在代码中看到的,在我创建的
<li>
中有两个 IMG,一个用于删除,另一个用于编辑。我想向删除 IMG 添加一个事件监听器,这样当用户单击它时,li 就会被消除。
问题是,如果元素尚不存在,我无法创建任何函数。我很乐意做这样的事:
const messageDeleteButton = document.querySelector("#message-delete");
messageDeleteButton.addEventListener("click", (e) => {
e.parentNode.parentNode.remove();
}
})
希望我说得足够清楚,谢谢大家!
您可以在 JS 中将 HTML 字符串解析为
document
,然后将事件侦听器添加到该元素。
因此,您获得了模板文字字符串。
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>
`;
}
您可以通过编写类似下面的函数将
string
转换为 HTML。
它使用
DOMParser API
,该 API 创建一个新
document
,其中将包含您在
string
中编写的所有 HTML。
const parseStringToHTML = (str) => {
const parser = new DOMParser();
return parser.parseFromString(str, 'text/html');
};
并且此文档的工作方式与您页面中的文档一样,它只是一个暂时仅存在于 JS 内存中的新文档。将您的
string
作为参数提供给
parseStringToHTML
以创建 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.
现在您的
string
是一个
document
,您可以在其上使用方法,例如
getElementById
、
querySelector
等。但
首先
您必须选择所需的元素。
const messageDeleteButton = messageHTML.querySelector("#message-delete");
请注意,我使用的是
messageHTML
而不是
document
。在这种情况下,
messageHTML
是
一个
document
,因此我们可以在其中进行查询。现在您已在此
document
中找到了元素,您可以为其添加事件侦听器。
messageDeleteButton.addEventListener("click", (e) => {
e.parentNode.parentNode.remove();
});
好的,现在事件侦听器已添加。您现在要做的就是将
messageHTML
中所需的 HTML 附加到页面的
document
中。您可以在
messageHTML.body
属性中找到 HTML。
现在,不要使用
insertAdjacentHTML
,而是使用
insertAdjacentElement
将元素插入到您选择的位置。您要附加的元素是
<li class="message-item">
,它将是
messageHTML.body
属性的
body
中的
firstElementChild
。
因此,在您表单的
submit
事件监听器中,将以下行从:
messageList.insertAdjacentHTML("beforeend", messageFormatted(messageTextarea.value));
更改为我上面解释的所有内容。
// 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);
请不要忘记在代码中包含
parseStringToHTML
函数。我知道这需要做很多工作,所以如果您有任何问题,请提出。
我希望这会对您有所帮助。
注意
我发现事件委托(全局事件监听器)不是您想要做的事情,尽管正如其他人所说,它比其他方法(甚至是我自己的答案)更高效且更容易实现。它还可以解决监听您已添加甚至从列表中删除的元素的问题。您甚至可以在
<ul id="message-list">
元素上设置它,就像
T.J. Crowder 建议的
。
您走在正确的轨道上。标识特征不一定是
id
,它可以是元素的任何内容——类是一种常见的方法。
有三种 DOM 方法可以帮助实现灵活性:
-
closest
- 从您调用它的元素开始,找到与给定 CSS 选择器匹配的最近祖先 -
contains
- 检查节点是否包含另一个节点 -
matches
- 检查调用它的元素是否与给定的 CSS 选择器匹配
例如,您当前的处理程序要求按钮是被点击的元素 (
event.target
)。这对于您的
img
用例来说没问题,但如果它是一个里面有
img
的
button
呢?那么
event.target
可能是
img
的
button
,具体取决于用户点击的位置。
closest
和
contains
可帮助实现这一点:
document.addEventListener("click", (e) => {
const button = e.target.closest("#message-delete");
if (button) {
button.closest("li").remove();
messageNumber--;
messageEmptyCheck();
}
})
您还需要将事件挂接到最接近的容器上。有时,您会被困在
document
或
document.body
上。但很多时候,您可以做得更好。例如,当监听表格单元格中的按钮上的按钮点击时,您可以在
table
或
tbody
/
thead
级别而不是 document 级别上监听。这就是
contains
发挥作用的地方,它确保
closest
不会在文档树中走得太远:
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();
}
})
在您的情况下,对
#message-delete
的搜索不太可能超出包含元素的范围,因此您可能不需要这一点,但有时这种情况会发生,因此您需要
contains
检查。
如果您确实想为该元素本身添加事件监听器,那么我认为您可能想尝试 MutationObserver - https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver
您可以通过传递 childList、subtree 的配置来观察事件,并且在事件中您可以检查您要查找的元素是否已添加或是否存在。如果您发现可以添加事件监听器。
您可能需要查看以下文章 - https://eager.io/blog/three-real-world-use-cases-for-mutation-observer/ https://blog.sessionstack.com/how-javascript-works-tracking-changes-in-the-dom-using-mutationobserver-86adc7446401
希望对您有所帮助。