Event binding on dynamically created elements?

1,016,520

Solution 1

As of jQuery 1.7 you should use jQuery.fn.on with the selector parameter filled:

$(staticAncestors).on(eventName, dynamicChild, function() {});

Explanation:

This is called event delegation and works as followed. The event is attached to a static parent (staticAncestors) of the element that should be handled. This jQuery handler is triggered every time the event triggers on this element or one of the descendant elements. The handler then checks if the element that triggered the event matches your selector (dynamicChild). When there is a match then your custom handler function is executed.


Prior to this, the recommended approach was to use live():

$(selector).live( eventName, function(){} );

However, live() was deprecated in 1.7 in favour of on(), and completely removed in 1.9. The live() signature:

$(selector).live( eventName, function(){} );

... can be replaced with the following on() signature:

$(document).on( eventName, selector, function(){} );

For example, if your page was dynamically creating elements with the class name dosomething you would bind the event to a parent which already exists (this is the nub of the problem here, you need something that exists to bind to, don't bind to the dynamic content), this can be (and the easiest option) is document. Though bear in mind document may not be the most efficient option.

$(document).on('mouseover mouseout', '.dosomething', function(){
    // what you want to happen when mouseover and mouseout 
    // occurs on elements that match '.dosomething'
});

Any parent that exists at the time the event is bound is fine. For example

$('.buttons').on('click', 'button', function(){
    // do something here
});

would apply to

<div class="buttons">
    <!-- <button>s that are generated dynamically and added here -->
</div>

Solution 2

There is a good explanation in the documentation of jQuery.fn.on.

In short:

Event handlers are bound only to the currently selected elements; they must exist on the page at the time your code makes the call to .on().

Thus in the following example #dataTable tbody tr must exist before the code is generated.

$("#dataTable tbody tr").on("click", function(event){
    console.log($(this).text());
});

If new HTML is being injected into the page, it is preferable to use delegated events to attach an event handler, as described next.

Delegated events have the advantage that they can process events from descendant elements that are added to the document at a later time. For example, if the table exists, but the rows are added dynamically using code, the following will handle it:

$("#dataTable tbody").on("click", "tr", function(event){
    console.log($(this).text());
});

In addition to their ability to handle events on descendant elements which are not yet created, another advantage of delegated events is their potential for much lower overhead when many elements must be monitored. On a data table with 1,000 rows in its tbody, the first code example attaches a handler to 1,000 elements.

A delegated-events approach (the second code example) attaches an event handler to only one element, the tbody, and the event only needs to bubble up one level (from the clicked tr to tbody).

Note: Delegated events do not work for SVG.

Solution 3

This is a pure JavaScript solution without any libraries or plugins:

document.addEventListener('click', function (e) {
    if (hasClass(e.target, 'bu')) {
        // .bu clicked
        // Do your thing
    } else if (hasClass(e.target, 'test')) {
        // .test clicked
        // Do your other thing
    }
}, false);

where hasClass is

function hasClass(elem, className) {
    return elem.className.split(' ').indexOf(className) > -1;
}

Live demo

Credit goes to Dave and Sime Vidas

Using more modern JS, hasClass can be implemented as:

function hasClass(elem, className) {
    return elem.classList.contains(className);
}

The same jsfiddle Live demo embeded below:

function hasClass(elem, className) {
  return elem.classList.contains(className);
}

document.addEventListener('click', function(e) {
  if (hasClass(e.target, 'bu')) {
    alert('bu');
    document.querySelector('.bu').innerHTML = '<div class="bu">Bu<div class="tu">Tu</div></div>';
  } else if (hasClass(e.target, 'test')) {
    alert('test');
  } else if (hasClass(e.target, 'tu')) {
    alert('tu');
  }

}, false);
.test,
.bu,
.tu {
  border: 1px solid gray;
  padding: 10px;
  margin: 10px;
}
<div class="test">Test
  <div class="bu">Bu</div>test
</div>

Solution 4

You can add events to objects when you create them. If you are adding the same events to multiple objects at different times, creating a named function might be the way to go.

var mouseOverHandler = function() {
    // Do stuff
};
var mouseOutHandler = function () {
    // Do stuff
};

$(function() {
    // On the document load, apply to existing elements
    $('select').hover(mouseOverHandler, mouseOutHandler);
});

// This next part would be in the callback from your Ajax call
$("<select></select>")
    .append( /* Your <option>s */ )
    .hover(mouseOverHandler, mouseOutHandler)
    .appendTo( /* Wherever you need the select box */ )
;

Solution 5

You could simply wrap your event binding call up into a function and then invoke it twice: once on document ready and once after your event that adds the new DOM elements. If you do that you'll want to avoid binding the same event twice on the existing elements so you'll need either unbind the existing events or (better) only bind to the DOM elements that are newly created. The code would look something like this:

function addCallbacks(eles){
    eles.hover(function(){alert("gotcha!")});
}

$(document).ready(function(){
    addCallbacks($(".myEles"))
});

// ... add elements ...
addCallbacks($(".myNewElements"))
Share:
1,016,520
Eli
Author by

Eli

Updated on July 30, 2022

Comments

  • Eli
    Eli almost 2 years

    I have a bit of code where I am looping through all the select boxes on a page and binding a .hover event to them to do a bit of twiddling with their width on mouse on/off.

    This happens on page ready and works just fine.

    The problem I have is that any select boxes I add via Ajax or DOM after the initial loop won't have the event bound.

    I have found this plugin (jQuery Live Query Plugin), but before I add another 5k to my pages with a plugin, I want to see if anyone knows a way to do this, either with jQuery directly or by another option.

  • theflowersoftime
    theflowersoftime over 12 years
    This post really helped me get a grasp on a problem I was having loading the same form and getting 1,2,4,8,16... submissions. Instead of using .live() I just used .bind() in my .load() callback. Problem solved. Thanks!
  • Felix Kling
    Felix Kling almost 11 years
    Learn more about event delegation here: learn.jquery.com/events/event-delegation.
  • chridam
    chridam almost 10 years
    The method live() was deprecated in version 1.7 in favor of on and deleted in version 1.9.
  • zloctb
    zloctb over 8 years
  • MadeInDreams
    MadeInDreams over 8 years
    Yeap and they are not saying (document.body) its says ancestor wich could be pretty much anything
  • Eugen Konkov
    Eugen Konkov almost 8 years
    You may use Element.classList instead of splitting
  • Ram Patra
    Ram Patra almost 8 years
    @EugenKonkov Element.classList is not supported supported on older browsers. For example, IE < 9.
  • ddlab
    ddlab about 7 years
    Your code contains 1 mistake: myElement.append('body'); must be myElement.appendTo('body');. On the other hand, if there is no need for the further use of variable myElement it's easier and shorter this way: $('body').append($('<button/>', { text: 'Go to Google!' }).bind( 'click', goToGoogle));
  • Ram Patra
    Ram Patra almost 7 years
    A nice article on how to get things done using vanilla script instead of jQuery - toddmotto.com/…
  • Andreas Trantidis
    Andreas Trantidis almost 7 years
    how about bubbling? What if the click event happened on a child of the element you are interested in?
  • Ram Patra
    Ram Patra almost 7 years
    @AndreasTrantidis you have to check for the class of the child element: jsfiddle.net/ramswaroop/d8e1860r
  • Palec
    Palec over 6 years
    While this code snippet may solve the problem, it doesn't explain why or how it answers the question. Please include an explanation for your code, as that really helps to improve the quality of your post. Remember that you are answering the question for readers in the future, and those people might not know the reasons for your code suggestion.
  • Rory McCrossan
    Rory McCrossan almost 6 years
    delegate() is now deprecated. Do not use it.
  • William
    William over 5 years
    I prefer this implementation; I just have to set up a call back
  • Fabian Bigler
    Fabian Bigler over 5 years
    The unbinding does not work, this simply adds another event which points to an empty function...
  • Sebastian Simon
    Sebastian Simon almost 4 years
    It’s also possible to assign var body = $("body").on(); directly.
  • Asad Ali
    Asad Ali almost 4 years
    Yes, but it'll create event propagation for elements. you need to add target the specific dynamic element and stop event propagation.
  • Mustkeem K
    Mustkeem K almost 4 years
    In case you bind same event on parent and descendent element than you can stop it using event.stopPropagation()
  • Mark Baijens
    Mark Baijens over 3 years
    You should aim to bind it to the closest static parent not the whole document.
  • Mark Baijens
    Mark Baijens over 3 years
    You should aim to bind it to the closest static parent not the whole document.
  • oldboy
    oldboy over 3 years
    this cannot be practical. would it not just be better to loop thru the items and add the event directly instead of checking the class every time the event is triggered?
  • Sebastian Simon
    Sebastian Simon over 2 years
    Nowadays, you’d just do addEventListener("click", ({ target }) => { const element = target.closest(selector); if(element){} }); for any selector.
  • Sebastian
    Sebastian over 2 years
    That solution still brings errors in jQuery 3 and firefox as after clearing html and recreating it the event is fired twice,
  • HoldOffHunger
    HoldOffHunger over 2 years
    While this solution mentions "event delegation" at least ten times, it no place does it actually show you how to delegate an event to an dynamically bound function.