jquery detecting div of certain class has been added to DOM

Jquery

Jquery Problem Overview


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.

Jquery Solutions


Solution 1 - Jquery

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);

Solution 2 - Jquery

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

Solution 3 - Jquery

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).

Solution 4 - Jquery

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.

Solution 5 - Jquery

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();

Solution 6 - Jquery

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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionfrenchieView Question on Stackoverflow
Solution 1 - JqueryadeneoView Answer on Stackoverflow
Solution 2 - JqueryAdam PietrasiakView Answer on Stackoverflow
Solution 3 - JqueryJohn CleggView Answer on Stackoverflow
Solution 4 - JqueryfrenchieView Answer on Stackoverflow
Solution 5 - JqueryraddrickView Answer on Stackoverflow
Solution 6 - JqueryagrulView Answer on Stackoverflow