I've been doing a lot of reading on closures today, and have looked through quite a few similar questions. I haven't been able to find one that touches on the exact same issue I'm having and how to fix it. Most answers seem to be jQuery related, but there are some fantastic answers out there on how closures work. I know that's where my problem is here. I've tried many different closure examples with no luck.
I have a set of data representing 3 campuses. Each campus has some buildings in it. The first for loop runs through the campuses. The nested for loop runs through the buildings in the current campus and binds a click event to it (which allows me to pan and zoom a leaflet map programmatically.)
I've created a very simplified JSFiddle to represent what I'm trying to do, which shows the issue. Only the buildings in the last "campus" group retain the click event.
Here's the JS code from JS Fiddle. http://jsfiddle.net/scwutpno/3/
var campuses = {
"campus1": {
"buildings": {
"building1": {
"id": 1
},
"building2": {
"id": 2
},
"building3": {
"id": 3
}
},
"name": "Campus 1"
},
"campus2": {
"buildings": {
"building1": {
"id": 4
},
"building2": {
"id": 5
},
"building3": {
"id": 6
}
},
"name": "Campus 2"
},
"campus3": {
"buildings": {
"building1": {
"id": 7
},
"building2": {
"id": 8
},
"building3": {
"id": 9
}
},
"name": "Campus 3"
}
};
var buildingLinksContainer = document.getElementById("building-list");
function buildExternalLinks() {
for (var campus in campuses) {
var container,
item;
if (buildingLinksContainer !== null) {
buildingLinksContainer.innerHTML += '<div class="building-links" id="' + campus + '-buildings">' +
'<div class="building-links__campus-name">' + campuses[campus].name + '</div></div>';
container = document.getElementById(campus + '-buildings');
}
for (var building in campuses[campus].buildings) {
item = container.appendChild(document.createElement('li'));
item.innerHTML = 'Building ' + campuses[campus].buildings[building].id;
item.addEventListener("click", function () {
alert('clicked!');
});
}
}
}
buildExternalLinks();
I've tried putting the click event in a self-invoking closure, but I don't think it's the data inside the click event that's having issues. It's the node that the click event is being applied to. I don't know if because I'm using a nested for loop, I need some sort of nested closure concept?
Would love to see how this issue is solved. I've already overcome it once by using a closure in a for loop, but this nested for loop just has me stumped.
The problem is that your code rewrites the DOM each time you append to the "buildingLinksContainer" element. Every time your code appends to the "buildingLinksContainer"'s innerHTML with "+=", the DOM is created from scratch. All elements that previously had event listeners inside "buildingLinksContainer" are created as new elements without any event listeners.
Only on the last iteration, when the DOM isn't torn down afterwards, do the event listeners stick. And since only the last three li elements are created in the last iteration, only they have event listeners.
The culprit:
buildingLinksContainer.innerHTML += '<div class="building-links" id="' + campus + '-buildings">' +
'<div class="building-links__campus-name">' + campuses[campus].name + '</div></div>';
I've rewritten your solution in a way that works (with the bonus of better performance and markup)
var buildingLinksContainer = document.getElementById("building-list");
function buildExternalLinks(campuses, rootElement) {
var ul = document.createElement('ul'),
li = document.createElement('li'),
h1 = document.createElement('h1'),
list = document.createDocumentFragment();
for(var campus in campuses) {
if(campuses.hasOwnProperty(campus)) {
var c = ul.cloneNode(false);
c.classList.add('building-links');
c.setAttribute('id', campus+'-buildings');
var name = h1.cloneNode(false);
name.appendChild(document.createTextNode(campuses[campus].name));
c.appendChild(name);
for(var building in campuses[campus].buildings) {
if(campuses[campus].buildings.hasOwnProperty(building)) {
var b = li.cloneNode(false);
var text = 'Building ' + campuses[campus].buildings[building].id;
b.appendChild(document.createTextNode(text));
b.addEventListener('click', function(event) {
alert(event.target.innerHTML);
});
c.appendChild(b);
}
}
}
list.appendChild(c);
}
rootElement.appendChild(list);
}
buildExternalLinks(campuses, buildingLinksContainer);
I've also updated/forked your JSFiddle.