英文:
Why are functions being called multiple times on dynamically generated button in JavaScript?
问题 {#heading}
我正在制作一个应用程序,从Google Places API获取搜索结果列表,用户可以通过在JavaScript中动态生成的按钮将位置标记为已访问(然后将其存储在MongoDB中)。当我点击按钮时,我看到事件运行了大约20次(偶然地,搜索结果中返回的地点数量也是相同的)。然后在服务器端调用API端点约5次。
我怀疑这与将事件侦听器绑定到每个单独的按钮有关,但我不确定修复方法是什么。非常感谢任何帮助!
客户端函数:
const markVisitedButtons = document.querySelectorAll(".visitor");
markVisitedButtons.forEach((button) => {
button.addEventListener("click", function () {
console.log("eventlistener");
const placeId = this.getAttribute("data-place-id");
const placeName = this.getAttribute("placeName");
const placeAddress = this.getAttribute("placeAddress");
markVisited(placeName);
});
});
function markVisited(placeName) {
console.log("fetch function");
fetch(`/markVisited`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ placeName }),
})
.then((response) => response.json())
.then((data) => {
console.log(data);
})
.catch((error) => {
console.log(error);
});
}
在fetch之后调用的服务器端函数:
router.post("/markVisited", async (req, res, next) => {
const placeName = req.body;
try {
const newPlace = await new VisitedPlace({
placeName: req.body.placeName,
userId: req.user._id,
});
newPlace
.save()
.then(
User.updateOne(
{ _id: req.user._id },
{ $push: { visitedPlaces: newPlace._id } }
)
);
} catch (error) {
console.log(error);
}
});
英文:
I'm making an application that draws up a list of search results from Google Places API, which gives the user the opportunity to mark a location as visited (which is then stored in MongoDB) using buttons generated dynamically in JavaScript. When I click the button, I see that the event has run about 20 times (incidentally, the same number of places are returned in the search results). This then calls the API endpoint on the server side about 5 times.
I suspect it's something to do with binding the event listener to each individual button but I'm not sure what the fix is. Any help greatly appreciated!
Client-side functions:
const markVisitedButtons = document.querySelectorAll(".visitor");
markVisitedButtons.forEach((button) => {
button.addEventListener("click", function () {
console.log("eventlistener");
const placeId = this.getAttribute("data-place-id");
const placeName = this.getAttribute("placeName");
const placeAddress = this.getAttribute("placeAddress");
markVisited(placeName);
});
});
function markVisited(placeName) {
console.log("fetch function");
fetch(`/markVisited`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ placeName }),
})
.then((response) => response.json())
.then((data) => {
console.log(data);
})
.catch((error) => {
console.log(error);
});
}
And the server-side function called following the fetch:
router.post("/markVisited", async (req, res, next) =\> {
const placeName = req.body;
try {
const newPlace = await new VisitedPlace({
placeName: req.body.placeName,
userId: req.user._id,
});
newPlace
.save()
.then(
User.updateOne(
{ _id: req.user._id },
{ $push: { visitedPlaces: newPlace._id } }
)
);
} catch (error) {
console.log(error);
}
});
答案1 {#1}
得分: 2
为了解决这个问题,您可以尝试使用事件委托。事件委托意味着将单个事件侦听器附加到按钮的共同父元素,然后使用事件对象来确定点击了哪个具体的按钮。
以下是您的客户端端函数:
const markVisitedButtonsContainer = document.querySelector(".container"); // 使用共同的父元素
markVisitedButtonsContainer.addEventListener("click", function (event) {
if (event.target.classList.contains("visitor")) {
const placeId = event.target.getAttribute("data-place-id");
const placeName = event.target.getAttribute("placeName");
const placeAddress = event.target.getAttribute("placeAddress");
markVisited(placeName);
}
});
function markVisited(placeName) {
console.log("fetch function");
fetch(`/markVisited`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ placeName }),
})
.then((response) => response.json())
.then((data) => {
console.log(data);
})
.catch((error) => {
console.log(error);
});
}
我已添加一个容器元素(将.container
替换为实际包含按钮的类或ID),作为按钮的共同父元素。事件侦听器附加到此容器,它会检查点击的元素是否具有类名"visitor"。如果是,事件将被处理。这样,无论按钮的数量多少,您都只有一个事件侦听器,事件不会多次传播。
英文:
To fix this, you can try using event delegation. Event delegation means attaching a single event listener to a common parent element of the buttons and then using the event object to determine which specific button was clicked.
Here is your client side functions:
const markVisitedButtonsContainer = document.querySelector(".container"); // Use a common parent element
markVisitedButtonsContainer.addEventListener("click", function (event) {
if (event.target.classList.contains("visitor")) {
const placeId = event.target.getAttribute("data-place-id");
const placeName = event.target.getAttribute("placeName");
const placeAddress = event.target.getAttribute("placeAddress");
markVisited(placeName);
}
});
`function markVisited(placeName) {
console.log("fetch function");
fetch(``/markVisited``, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ placeName }),
})
.then((response) => response.json())
.then((data) => {
console.log(data);
})
.catch((error) => {
console.log(error);
});
}
`
I've added a container element (replace .container with the actual class or id of the container that holds your buttons) as the common parent element of the buttons. The event listener is attached to this container, and it checks whether the clicked element has the class visitor. If it does, the event is processed. This way, you ensure that you have only one event listener regardless of the number of buttons, and the event won't propagate multiple times.