Reputation: 143
I have some code that dynamically adds links to the DOM and adds listeners to each one. The problem I was having was that each time I'd click on one of the links, all of the handlers would fire. I tried fixing this, and the result was always either this or I'd get no response at all (as if the listener was never assigned).
So I just fixed the problem by doing something I didn't think worked. I assigned the listener to the id of the element being dynamically added (instead of assigning it to a parent element already in the DOM). I thought this wasn't possible because the listener had to be on an element that was already in the DOM?
What makes this even more confusing is that I was playing with some very similar code in jsfiddle, and its results were pretty much the opposite of what I was getting. When I was having problems, it worked fine, and when I solved my problem, the solution didn't work in jsfiddle!
So here's my working code. Just a bit more background - I'm using Firebase, and these li elements are being added every time a child_added event occurs in the DB (or when a user logs in, the list will populate with all entries already in the DB).
var songName = songSnapshot.name();
$(listSelector).append("<li id='" + songName + "'><a href='#'>" + songName + "</a></li>");
$("#" + songName).on("click", function(event) {
event.preventDefault();
var clickedSong = $(this).text();
loadCheck(clickedSong);
});
And here's the HTML:
<ul class="six columns" id="dropdowns">
<li><a href="#" id="createsong">Create New Song</a></li>
<li>
<a href="#">Your Songs</a>
<div class="dropdown">
<ul id="userSonglist">
</ul>
</div>
</li>
<li>
<a href="#">All Songs</a>
<div class="dropdown">
<ul id="publicSonglist">
</ul>
</div>
</li>
</ul>
Any ideas what's going on, why this is working? Is my understanding of jquery listeners incorrect? I've spent pretty much an entire day researching this here, on jquery.com, and other sites, and still this baffles me. I actually just tried this code in jsfiddle, and it worked. So obviously I'm missing/misunderstanding something.
Upvotes: 1
Views: 97
Reputation: 76774
Thanks for such a comprehensive question, further to the current answers:
Dynamic DOM
JQuery is built on top of Javascript, so your issue is not just a JQuery one
The way both of these technologies work is to take the elements of the document & assign functions to them at load. This means that any element added after load has to be assigned an eventListener / function on the fly
The problem you're facing is that your elements don't have a function bound to them, as they don't exist when JS assigns its functions/listeners. This means that, independently of the element addition process, you have to be able to bind the event to your new elements when they are added:
The correct terminology for your fix is to "delegate" your JS events
Delegating basically means that instead of listening to specific elements, JS will actually listen to a parent element, and will then "delegate" its function to all the children of it. This means that if the children are added dynamically, the event will still catch them
Here's some code for you:
$("#dropdowns").on("click", "li", function(event) {
event.preventDefault();
var clickedSong = $(this).text();
loadCheck(clickedSong);
});
As you can see, the typical way to delegate the event is to put it on the parent element, but you can always attach to $(document)
too (as this is the overall parent for the page)
Upvotes: 1
Reputation: 10216
What are you misunderstanding in particular? Your code is perfectly fine.
var songName = songSnapshot.name();
if (listSelector === "#publicSonglist") {
$(listSelector).append("<li id='" + songName + "'><a href='#'>" + songName + "</a></li>");
}
else {
$(listSelector).append("<li id='" + songName + "'><a href='#'>" + songName + "</a></li>");
};
At this point the element as been added to the dom so you can actually bind events to it. Even if it hasnt been added to the dom, you can select the parent an use event delegation using jQuery on():
Delegated events have the advantage that they can process events from descendant elements that are added to the document at a later time.
So you would do something like:
$('#dropdowns').on("click", '#'+songName, function(event) {
event.preventDefault();
var clickedSong = $(this).text();
loadCheck(clickedSong);
});
Or even more generous since you dont really need the id at all:
$('#dropdowns').on("click", '.songItem', function(event) {
event.preventDefault();
var clickedSong = $(this).text();
loadCheck(clickedSong);
});
Hope that helps you understand :)
Upvotes: 1
Reputation: 9167
What I would do is something like this...
// P.s. this if/else statement are doing the exact same thing?!
var songName = songSnapshot.name();
if (listSelector === "#publicSonglist") {
$(listSelector).append("<li id='" + songName + "' class='songItem'><a href='#'>" + songName + "</a></li>");
}
else {
$(listSelector).append("<li id='" + songName + "' class='songItem'><a href='#'>" + songName + "</a></li>");
};
Then, outside your click/add event:
$('#dropdowns').on("click", '.songItem', function(event) {
event.preventDefault();
var clickedSong = $(this).text();
loadCheck(clickedSong);
});
Upvotes: 2