Reputation: 13
I have a bunch of
<div class="location-box" data-location-id="123">
<img src="img_url" />
</div>
Loaded into my .locations
div.
I want that whenever you click on a .location-box
that the clicked div gets a highlighted class on it. And the attribute value gets added to a hidden input. When you click on another one, the class from the previous one gets removed. And so on and so on.
I've tried it before when those divs where static, and it worked fine. But now I'm appending these divs out of pure Javascript from an api call.
I also know that not yet generated DOM can't be manupilated by event listeners etc.
I've looked into mutation observers, and tried some simple stuff from the docs. But I could make this code work with it
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");
})
);
Does anyone know how to make this work? Maybe not only this time, but in multiple cases. Cause in the near future I'd probably have more not yet generated DOM.
Upvotes: 1
Views: 1572
Reputation: 13417
From my above comment ...
"@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>
Upvotes: 2
Reputation: 5060
You can do that with MutationObserver
, the code it's something like below, it doesn't have the piece to get the attribute, but you can add that, another way of doing it would be like @scara9 is saying, on the code you use to render each .location-box
you can assign the click handler.
in the code below i used jquery to "add" new location-box
, you don't need jquery for this
// 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>
Upvotes: 0