在尚未生成的 DOM 上添加事件监听器
我有一堆
<div class="location-box" data-location-id="123">
<img src="img_url" />
</div>
已加载到我的
.locations
div 中。
我希望每当您单击
.location-box
时,单击的 div 都会获得突出显示的类。并且属性值会添加到隐藏的输入中。当您单击另一个时,前一个的类将被删除。依此类推。
我以前尝试过这些 div 是静态的,效果很好。但现在我要从 api 调用中用纯 Javascript 附加这些 div。
我还知道尚未生成的 DOM 不能被事件侦听器等操纵。
我研究过突变观察者,并尝试了文档中的一些简单的东西。但我可以让这段代码与它一起工作
let locations = document.querySelectorAll(".location-box");
locations.forEach( el =>
el.addEventListener('click', function() {
locations.forEach( els => els.classList.remove('active-location'));
document.getElementById('location_id').value = this.getAttribute('data-location-id');
this.classList.add("active-location");
})
);
有人知道如何让它工作吗?也许不只是这一次,而是在很多情况下。因为在不久的将来我可能会有更多尚未生成的 DOM。
从我上面的评论...
"@Coolguy31 ... A
MutationObserver
based approach most likely is overkill. Event Delegation might be the technique of choice. But in order to implement it somehow correctly it was nice to know whether all the later rendered stuff is always inserted/appended below a common and also known root node, causedocument.body
as event listening root is not the most elegant/performant choice either."
function uuid(a) {
// [https://gist.github.com/jed/982883] - Jed Schmidt
return a
? (a^Math.random()*16>>a/4).toString(16)
: ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g,uuid);
}
function addLocations(evt) {
evt.preventDefault();
const allLocationsRoot =
document.querySelector('.locations');
allLocationsRoot.innerHTML = `
<div class="location-box">
<img src="https://picsum.photos/133/100?grayscale" />
</div>
<div class="location-box">
<img src="https://picsum.photos/100/75?grayscale" />
</div>
<div class="location-box">
<img src="https://picsum.photos/120/90?grayscale" />
</div>
`;
allLocationsRoot
.querySelectorAll('.location-box')
.forEach(locationNode => locationNode.dataset.locationId = uuid());
allLocationsRoot
.closest('form[name="location-data"]')
.elements['location']
.value = '';
}
function initializeAddLocations() {
document
.querySelector('button')
.addEventListener('click', addLocations);
}
function handleLocationSelect(evt) {
const locationItemRoot = evt
.target
.closest('.location-box');
if (locationItemRoot) {
const allLocationsRoot = locationItemRoot
.closest('.locations');
const locationControl = allLocationsRoot
.closest('form[name="location-data"]')
.elements['location'];
// console.log({
// target: evt.target,
// locationItemRoot,
// allLocationsRoot,
// locationControl,
// });
allLocationsRoot
.querySelectorAll('.location-box')
.forEach(locationNode => locationNode.classList.remove('selected'));
locationItemRoot.classList.add('selected');
locationControl.value = locationItemRoot.dataset.locationId;
}
}
function initializeLocationHandling() {
document
.querySelector('.locations')
.addEventListener('click', handleLocationSelect)
}
function main() {
initializeLocationHandling();
initializeAddLocations();
}
main();
body { margin: 0; }
[type="text"][name="location"] { width: 23em; }
.locations:after { display: block; content: ''; clear: both; }
.location-box { float: left; margin: 4px; padding: 10px; min-height: 104px; background-color: #eee; }
.location-box.selected { outline: 2px solid #fc0; }
<form name="location-data">
<div class="locations">
</div>
<button>update locations</button>
<!--
<input type="hidden" name="location" />
//-->
<input type="text" name="location" disabled />
</form>
您可以使用
MutationObserver
来做到这一点,代码如下所示,它没有获取属性的部分,但您可以添加它,另一种方法就像@scara9所说的那样,在您用来呈现每个
.location-box
的代码上,您可以分配点击处理程序。
在下面的代码中,我使用 jquery 来“添加”新的
location-box
,您不需要 jquery 来实现这一点
// select the parent node: .locations, i switched to ID for test
var target = document.getElementById("locations");
// create an observer instance
var observer = new MutationObserver(function (mutations) {
//loop through the detected mutations(added controls)
mutations.forEach(function (mutation) {
//addedNodes contains all detected new controls
if (mutation && mutation.addedNodes) {
mutation.addedNodes.forEach(function (elm) {
if (elm && elm.className=="location-box") {
elm.addEventListener("click", function () {
elm.classList.add("active-location");
var locations = document.querySelectorAll(".location-box");
locations.forEach((e) => {
if (e != elm) {
//ignore clicked element
e.classList.remove("active-location");
}
});
});
}
});
}
});
});
// pass in the target node, as well as the observer options
observer.observe(target, {
childList: true
});
//you don't need this, it's only to simulate dynamic location-box
$(function () {$("button").on("click", function () {var count = $(".location-box").length + 1;$(".locations").append($("<div class='location-box' data-attribute-id='" +count +"'>location box:" +count +"</div>"));});});
.active-location{
background-color: yellow;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="locations" id="locations">
</div>
<!-- This button it's only to add the controls, not needed in your case-->
<button type="button" id="add">
Add
</button>