104

I'm using .on() to bind events of divs that get created after the page loads. It works fine for click, mouseenter... but I need to know when a new div of class MyClass has been added. I'm looking for this:

$('#MyContainer').on({

  wascreated: function () { DoSomething($(this)); }

}, '.MyClass');

How do I do this? I've managed to write my entire app without a plugin and I want to keep it that way.

Thanks.

frenchie
  • 51,731
  • 109
  • 304
  • 510
  • Is it your own code that's generating the elements? If so, you could check at the time you create it and act accordingly. – Surreal Dreams May 02 '12 at 14:07
  • Yes, that's true but With .on(), I can bind events at document.ready and then I don't have to worry about the binding when I generate the HTML. I'm looking for the same behavior with DoSomething. – frenchie May 02 '12 at 14:09
  • you can use $("div/your-selector").hasClass("MyClass"); to check if a div has the particular class or not. I guess you could put this api to your use. – KBN May 02 '12 at 14:10
  • .on() is just for events. You could set up for a custom event, but you'd still have to trigger that event when you created new elements. – Surreal Dreams May 02 '12 at 14:12
  • @xFourtyFourx that will only work on elements present in the DOM at the time of ready(), and frenchie is generating elements dynamically. – Surreal Dreams May 02 '12 at 14:13
  • 3
    Duplicate of [Event when element added to page](http://stackoverflow.com/questions/7434685/event-when-element-added-to-page) – Dan Dascalescu Jul 15 '15 at 02:33

6 Answers6

117

Previously one could hook into jQuery's domManip method to catch all jQuery dom manipulations and see what elements where inserted etc. but the jQuery team shut that down in jQuery 3.0+ as it's generally not a good solution to hook into jQuery methods that way, and they've made it so the internal domManip method no longer is available outside the core jQuery code.

Mutation Events have also been deprecated, as before one could do something like

$(document).on('DOMNodeInserted', function(e) {
    if ( $(e.target).hasClass('MyClass') ) {
       //element with .MyClass was inserted.
    }
});

this should be avoided, and today Mutation Observers should be used instead, which would work like this

var observer = new MutationObserver(function(mutations) {
    mutations.forEach(function(mutation) {
        console.log(mutation)
        if (mutation.addedNodes && mutation.addedNodes.length > 0) {
            // element added to DOM
            var hasClass = [].some.call(mutation.addedNodes, function(el) {
                return el.classList.contains('MyClass')
            });
            if (hasClass) {
                // element has class `MyClass`
                console.log('element ".MyClass" added');
            }
        }
    });
});

var config = {
    attributes: true,
    childList: true,
    characterData: true
};

observer.observe(document.body, config);
adeneo
  • 312,895
  • 29
  • 395
  • 388
  • Firefox and webkit should pretty much be OK, but support for this is somewhat poor in IE. It should work in IE9, but probably not so much in IE8. I know I've seen workarounds, and I have'nt really testet this in IE, so test it first, if it's not working then see if there is a workaround. There is a plugin that looks like it adds IE support [here](http://code.google.com/p/callimachus/source/browse/tags/0.11/webapps/callimachus/scripts/node-inserted.js?r=1175), but have'nt tried it, just found it in a Google search. – adeneo May 02 '12 at 14:44
  • 2
    I initially used your answer but the fact that it wasn't working cross-browser was a problem. Then, I quickly figured out a work-around to avoid this issue in the first place. And then, a few weeks back this issue of detecting node insertion came back again in my code. This time, the code I posted in my answer works EXACTLY like it should, cross-browser and nothing buggy. So that's why I moved my answer as accepted; that's how SO works, the OP decides which answer he accepts and the crowd votes on answers. Over time, people will vote and decide which answer suits them best. – frenchie May 21 '15 at 05:09
  • 2
    And also, I wanted to say that you have helped me several times with high-quality answers that saved my day. Changing the accepted answer to mine is purely based on the code and how it fits the problem I had to solve. – frenchie May 21 '15 at 05:12
  • 1
    got "Uncaught TypeError: Cannot read property 'contains' of undefined" with this code – gordie Sep 28 '19 at 14:48
  • Look for agrul's answer above to fix the Uncaught TypeError, plus also respond for subtree elements, if you need this, like I did. – Tami Jun 17 '21 at 20:41
55

Here is my plugin that does exacly that - jquery.initialize

Useage is the same like you'd use .each function, but with .initialize function on element, difference from .each is it will also initialize elements added in future without any additional code - no matter if you add it with AJAX or anything else.

Initialize have exacly the same syntax as with .each function

$(".some-element").initialize( function(){
    $(this).css("color", "blue");
});

But now if new element matching .some-element selector will appear on page, it will be instanty initialized. The way new item is added is not important, you dont need to care about any callbacks etc.

$("<div/>").addClass('some-element').appendTo("body"); //new element will have blue color!

Plugin is based on MutationObserver

Adam Pietrasiak
  • 12,773
  • 9
  • 78
  • 91
  • Thank you so much. – Brandon Harris Mar 13 '17 at 19:54
  • how to stop initialization? I want to stop watching once the stuff I was looking for is added... – Evgeny Vostok Dec 27 '17 at 10:27
  • $(".some-element").initialize[..] is deprecated. Use $.initialize(".addDialog", function () { $('.noData').hide(); }); instead. – Heimi Mar 22 '18 at 11:00
  • Is it possible to execute the `.initialize` function's `callback` **only** for elements that are dynamically added (e.g. via Ajax), and skip the callback for elements already present when the page initially loads? I'm trying to add a parameter to `options` in order to accomplish this, but so far I haven't got this to work. – Nevermore Apr 19 '18 at 15:37
  • You can add issue on github about that. For now, technically you could do `$('.myClass').addClass('ignore-initialize'); $.initialize('.myClass:not(.ignore-initialize)', callback);` – Adam Pietrasiak May 09 '18 at 09:02
  • This is awesome. Thanks! – MarkPraschan Jul 13 '18 at 19:28
27

After reviewing this and several other posts I tried to distill what I thought was the best of each into something simple that allowed me to detect when a class of elements is inserted and then act on those elements.

function onElementInserted(containerSelector, elementSelector, callback) {

    var onMutationsObserved = function(mutations) {
        mutations.forEach(function(mutation) {
            if (mutation.addedNodes.length) {
                var elements = $(mutation.addedNodes).find(elementSelector);
                for (var i = 0, len = elements.length; i < len; i++) {
                    callback(elements[i]);
                }
            }
        });
    };

    var target = $(containerSelector)[0];
    var config = { childList: true, subtree: true };
    var MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
    var observer = new MutationObserver(onMutationsObserved);    
    observer.observe(target, config);

}

onElementInserted('body', '.myTargetElement', function(element) {
    console.log(element);
});

Importantly for me, this allows a) the target element to exist at any depth within "addedNodes" and b) the ability to deal with the element only when initially inserted (no need to search the entire document setting or ignoring "already-processed" flags).

John Clegg
  • 348
  • 3
  • 6
  • 1
    Your condition should be `if(mutation.addedNodes.length)` because `addedNodes` and `removedNodes` arrays are always there in `mutation` of type `childList` (checked in IE11, Chrome53, FF49). Still +1, because yours is the only answer where `addedNodes` is considered (`callback` is called only when a node is added); not only on this thread but many other threads. Everyone else is just invoking the `callback` for all mutations, even if the node is being deleted. – Vivek Athalye Sep 28 '16 at 12:47
  • Nice - works great. Thanks. – Christopher Dec 01 '21 at 16:33
18

3 years of experience later, this is how I listen to "element of a certain class added to the DOM": you simply add a hook into the jQuery html() function, like this:

function Start() {

   var OldHtml = window.jQuery.fn.html;

   window.jQuery.fn.html = function () {

     var EnhancedHtml = OldHtml.apply(this, arguments);

     if (arguments.length && EnhancedHtml.find('.MyClass').length) {

         var TheElementAdded = EnhancedHtml.find('.MyClass'); //there it is
     }

     return EnhancedHtml;
   }
}

$(Start);

This works if you're using jQuery, which I do. And it doesn't rely on the browser-specific event DOMNodeInserted, which is not cross-browser compatible. I also added the same implementation for .prepend()

Overall, this works like a charm for me, and hopefully for you too.

frenchie
  • 51,731
  • 109
  • 304
  • 510
  • 6
    Hmm -- I'd clarify that this works if you're exclusively using jQuery's `html` helper to modify the DOM, not just "using jQuery" in general. Elements added using, say, `jQuery.append` won't trigger this event. – iameli May 28 '15 at 06:36
  • 1
    Yes, and that's why I clarified that I added the same implementation with .preprend() and if you use .append() then do the same thing with .append() – frenchie May 28 '15 at 12:00
  • There's a much faster approach, using [CSS3 animations](http://stackoverflow.com/questions/16618876/determining-if-a-html-element-has-been-added-to-the-dom-dynamically/21600707#21600707). The open-source library also lends to far simpler code: `insertionQ('#intercom-container').every(function(/element){ element.style.display = 'none'; });` – Dan Dascalescu Jul 15 '15 at 12:42
  • 3
    Why the downvote?? This actually works: no plugin and no hacky setTimeout function. – frenchie Jul 15 '15 at 16:07
  • 2
    Upvoted -- this is a valid option -- why in the world (!!!) has this been downvoted?????? Dan, CSS3 Animations are not XBC: http://caniuse.com/#search=keyframes; http://davidwalsh.name/detect-node-insertion. – Cody Oct 08 '15 at 16:46
6

you could use mutation events

http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-eventgroupings-mutationevents

EDIT

from MDN: https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Mutation_events

Deprecated This feature has been removed from the Web. Though some browsers may still support it, it is in the process of being dropped. Do not use it in old or new projects. Pages or Web apps using it may break at any time.

Mutation Observers are the proposed replacement for mutation events in DOM4. They are to be included in Firefox 14 and Chrome 18.

https://developer.mozilla.org/en/docs/Web/API/MutationObserver

MutationObserver provides developers a way to react to changes in a DOM. It is designed as a replacement for Mutation Events defined in the DOM3 Events specification.

Example usage

The following example was taken from http://hacks.mozilla.org/2012/05/dom-mutationobserver-reacting-to-dom-changes-without-killing-browser-performance/.

// select the target node
var target = document.querySelector('#some-id');

// create an observer instance
var observer = new MutationObserver(function(mutations) {
  mutations.forEach(function(mutation) {
    console.log(mutation.type);
  });    
});

// configuration of the observer:
var config = { attributes: true, childList: true, characterData: true };

// pass in the target node, as well as the observer options
observer.observe(target, config);

// later, you can stop observing
observer.disconnect();
raddrick
  • 4,274
  • 2
  • 27
  • 33
  • 2
    Mutation events are pretty much deprecated according to [mdn](https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Mutation_events), in favour of MutationObservers – Olga May 13 '14 at 17:44
  • Now that IE10 is EOL, it should be nearly safe to say [MutationObserver](http://caniuse.com/#search=mutationobserver) is widely supported. – rymo Jan 18 '16 at 03:26
1

Two additions to adeneo's answer:

(1) we can change the line

return el.classList.contains('MyClass');

To

if( el.classList ) {
    return el.classList.contains('MyClass');
} else {
    return false;
}

So we won't see the "Uncaught TypeError: Cannot read property 'contains' of undefined" error.

(2) add subtree: true in config to find added nodes recursively in all added elements.

agrul
  • 71
  • 1
  • 9
  • Thank you for subtree:true and fixing the error too – Tami Jun 17 '21 at 20:40
  • 1
    @agrul Couldn't you just provide the entire code with your modifications made? It'd make this answer much easier to use. – Jez Jun 01 '22 at 09:43