我想将侦听器添加到JavaScript中动态创建的元素中,但它似乎不起作用。它不会抛出任何错误,所以我不知道我首先要做什么。你有什么想法吗?
{
const API_KEY = "9829fe5eab6c963bbe4850df2d7de4aa";
const container = document.getElementById("container");
const flippers = document.getElementsByClassName("header__flipper");
const cityTemplate = () => {
const template = `<section class="weather">
<button class="header__flipper"><span aria-hidden="true">→</span></button>
<header class="header">
<h1 class="header__heading">dfgdfgd
</h1>
</header>
</section>`;
return template;
};
const addListeners = (collection, ev, fn) => {
for (let i = 0; i < collection.length; i++) {
collection[i].addEventListener(ev, fn, false);
}
}
const req = (id, key) => {
const url = `https://api.openweathermap.org/data/2.5/forecast?id=${id}&APPID=${key}`;
fetch(url).then((res) => {
res.json().then((data) => {
container.innerHTML += cityTemplate(data);
});
})
}
req("6695624", API_KEY);
req("6695624", API_KEY);
req("6695624", API_KEY);
addListeners(flippers, "click", () => {
alert("test");
})
}<div id="container"></div>
发布于 2017-10-13 07:42:14
问题是在fetch请求完成之前添加事件侦听器。在调用addListeners时,触发器尚未在DOM中。
我已经修改了req方法,以返回fetch的承诺。使用Promise.all,代码将等待到所有三个获取完成。这仍然不能完全解决问题,代码知道何时完成获取,但这与将req方法添加到DOM中的cityTemplate方法不同。
有两种解决办法:
setTimeout处理程序中使用Promise.all。这很可能会延迟添加事件侦听器足够长的时间,以便将模板添加到DOM中。我添加了一些console.log语句,这些语句将显示上一次呈现的日志行之前的Promise.all日志行。req方法返回您自己创建的承诺,而不是fetch承诺。在将cityTemplate添加到DOM之后,解析自创建的承诺。通过这种方式,您可以肯定地知道,在所有东西都在DOM中之前,Promise.all是不会实现的。解决方案1不是一个非常健壮的解决方案,应该避免。解决方案2提供了所需的控制类型。我的答案显示了解决方案2的基本设置,它不执行任何错误处理。
{
const API_KEY = "9829fe5eab6c963bbe4850df2d7de4aa";
const container = document.getElementById("container");
const flippers = document.getElementsByClassName("header__flipper");
const cityTemplate = () => {
const template = `<section class="weather">
<button class="header__flipper"><span aria-hidden="true">→</span></button>
<header class="header">
<h1 class="header__heading">dfgdfgd
</h1>
</header>
</section>`;
return template;
};
const addListeners = (collection, ev, fn) => {
for (let i = 0; i < collection.length; i++) {
collection[i].addEventListener(ev, fn, false);
}
}
const req = (id, key) => {
console.log(`getting ${id}`);
// Return a new promise, this promise will be fulfilled once the template
// has been added with the retrieved data.
return new Promise(resolve => {
const url = `https://api.openweathermap.org/data/2.5/forecast?id=${id}&APPID=${key}`;
// Get the data
fetch(url).then((res) => {
res.json().then((data) => {
// Add the template to the DOM
container.innerHTML += cityTemplate(data);
console.log(`rendered ${id}`);
// Relove that promise that was returned by the method.
resolve();
});
})
});
}
// Wait for all three promises to be done. These promises will be fulfilled after
// the DOM has been updated.
Promise.all([req("6695624", API_KEY), req("6695624", API_KEY), req("6695624", API_KEY)])
.then(() => {
console.log(`promise all done`);
// There is no longer a need for a timeout, due to the change to the
// req method.
addListeners(flippers, "click", () => {
alert("test");
})
});
}<div id="container"></div>
发布于 2017-10-13 07:21:37
核心问题是由于req()函数是异步的-这意味着调用req()函数,但它在将来的某个未知点结束。当每个req()都在等待完成脚本时,addListeners()函数将被调用,使用.header__flipper选择器-但是由于异步行为,.header__flipper元素还没有创建,所以没有添加事件侦听器。
作为一个演示,我向addListeners()函数添加了一个超时,以便它在被调用前等待1秒。这使req()函数有时间完成,并允许事件侦听器正确附加。
然而,setTimeout() 并不是解决方案,下面的片段仅用于演示问题,向下滚动以找到正确的解决方案。
{
const API_KEY = "9829fe5eab6c963bbe4850df2d7de4aa";
const container = document.getElementById("container");
const cityTemplate = () => {
const template = `<section class="weather">
<button class="header__flipper"><span aria-hidden="true">→</span></button>
<header class="header">
<h1 class="header__heading">dfgdfgd
</h1>
</header>
</section>`;
return template;
};
const addListeners = (collection, ev, fn) => {
for (let i = 0; i < Array.from(collection).length; i++) {
collection[i].addEventListener(ev, fn, false);
}
}
const req = (id, key) => {
const url = `https://api.openweathermap.org/data/2.5/forecast?id=${id}&APPID=${key}`;
fetch(url).then((res) => {
res.json().then((data) => {
container.innerHTML += cityTemplate(data);
});
})
}
req("6695624", API_KEY);
req("6695624", API_KEY);
req("6695624", API_KEY);
// For Demo Only
// The req() function is asynchronous so the addListeners() function was attempting to attach to the elements before they were created
window.setTimeout(function() {
addListeners(document.getElementsByClassName("header__flipper"), "click", () => {
alert("test");
})
}, 1000)
}<div id="container"></div>
解决方案
解决方案是将事件侦听器附加到父选择器(正如@Nishad所建议的)。其思想是将单击事件侦听器附加到父元素(如#container),并在侦听器回调函数中检查事件目标是否是新的动态元素之一。
在您的示例中,在按钮中添加<span class="header__flipper__aria" aria-hidden="true">→</span>会使事情复杂化一些,因为事件目标可以是<button>,也可以是<span>。这要求我们检查事件目标是否是这些元素中的任何一个。
{
const API_KEY = "9829fe5eab6c963bbe4850df2d7de4aa";
const container = document.getElementById("container");
const cityTemplate = () => {
const template = `<section class="weather">
<button class="header__flipper"><span class="header__flipper__aria" aria-hidden="true">→</span></button>
<header class="header">
<h1 class="header__heading">dfgdfgd
</h1>
</header>
</section>`;
return template;
};
const addListeners = (collection, ev, fn) => {
collection.addEventListener(ev, fn, false);
}
const req = (id, key) => {
const url = `https://api.openweathermap.org/data/2.5/forecast?id=${id}&APPID=${key}`;
fetch(url).then((res) => {
res.json().then((data) => {
container.innerHTML += cityTemplate(data);
});
})
}
req("6695624", API_KEY);
req("6695624", API_KEY);
req("6695624", API_KEY);
addListeners(document.getElementById("container"), "click", (event) => {
var classes = event.target.classList;
if (classes.contains("header__flipper") || classes.contains("header__flipper__aria")) {
alert("test");
}
})
}<div id="container"></div>
替代解
另一种方法是在创建动态元素时将事件侦听器附加到回调中每个动态元素的按钮上,如下所示:
{
const API_KEY = "9829fe5eab6c963bbe4850df2d7de4aa";
const container = document.getElementById("container");
const cityTemplate = () => {
const newEl = document.createElement("section");
newEl.classList.add("weather");
const template = `<button class="header__flipper"><span class="header__flipper__aria" aria-hidden="true">→</span></button>
<header class="header">
<h1 class="header__heading">dfgdfgd
</h1>
</header>`;
newEl.innerHTML = template;
return newEl;
};
const req = (id, key) => {
const url = `https://api.openweathermap.org/data/2.5/forecast?id=${id}&APPID=${key}`;
fetch(url).then((res) => {
res.json().then((data) => {
const city = cityTemplate(data);
city.querySelector("button").addEventListener("click", function(){
alert("test");
}, false);
container.appendChild(city);
});
})
}
req("6695624", API_KEY);
req("6695624", API_KEY);
req("6695624", API_KEY);
}<div id="container"></div>
发布于 2017-10-13 06:41:58
您需要使用下面的代码片段为动态创建的元素触发单击事件
$(document).on("click", ".className", function(){
alert('this works')
});对于JavaScript解决方案,您可以参考以下内容:adding onclick event to dynamically added button?
https://stackoverflow.com/questions/46723864
复制相似问题