By esafwan


2010-07-10 15:21:07 8 Comments

I want to execute a function when some div or input are added to the html. Is this possible?

For example, a text input is added, then the function should be called.

7 comments

@Anthony Awuley 2018-05-23 16:59:53

Use the MutationObserver interface as shown in Gabriele Romanato's blog

Chrome 18+, Firefox 14+, IE 11+, Safari 6+

// The node to be monitored
var target = $( "#content" )[0];

// Create an observer instance
var observer = new MutationObserver(function( mutations ) {
  mutations.forEach(function( mutation ) {
    var newNodes = mutation.addedNodes; // DOM NodeList
    if( newNodes !== null ) { // If there are new nodes added
        var $nodes = $( newNodes ); // jQuery set
        $nodes.each(function() {
            var $node = $( this );
            if( $node.hasClass( "message" ) ) {
                // do something
            }
        });
    }
  });    
});

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

@Benjamin 2018-10-04 22:27:40

MutationObserver is native JavaScript, not jQuery.

@vsync 2013-01-28 20:27:47

This is the ultimate approach so far, with smallest code:

IE9+, FF, Webkit:

Using MutationObserver and falling back to the deprecated Mutation events if needed:
(Example below if only for DOM changes concerning nodes appended or removed)

var observeDOM = (function(){
  var MutationObserver = window.MutationObserver || window.WebKitMutationObserver;

  return function( obj, callback ){
    if( !obj || !obj.nodeType === 1 ) return; // validation

    if( MutationObserver ){
      // define a new observer
      var obs = new MutationObserver(function(mutations, observer){
          callback(mutations);
      })
      // have the observer observe foo for changes in children
      obs.observe( obj, { childList:true, subtree:true });
    }
    
    else if( window.addEventListener ){
      obj.addEventListener('DOMNodeInserted', callback, false);
      obj.addEventListener('DOMNodeRemoved', callback, false);
    }
  }
})();

//------------< DEMO BELOW >----------------
// add item
var itemHTML = "<li><button>list item (click to delete)</button></li>",
    listElm = document.querySelector('ol');

document.querySelector('body > button').onclick = function(e){
  listElm.insertAdjacentHTML("beforeend", itemHTML);
}

// delete item
listElm.onclick = function(e){
  if( e.target.nodeName == "BUTTON" )
    e.target.parentNode.parentNode.removeChild(e.target.parentNode);
}
    
// Observe a specific DOM element:
observeDOM( listElm, function(m){ 
   var addedNodes = [], removedNodes = [];

   m.forEach(record => record.addedNodes.length & addedNodes.push(...record.addedNodes))
   
   m.forEach(record => record.removedNodes.length & removedNodes.push(...record.removedNodes))

  console.clear();
  console.log('Added:', addedNodes, 'Removed:', removedNodes);
});


// Insert 3 DOM nodes at once after 3 seconds
setTimeout(function(){
   listElm.removeChild(listElm.lastElementChild);
   listElm.insertAdjacentHTML("beforeend", Array(4).join(itemHTML));
}, 3000);
<button>Add Item</button>
<ol>
  <li><button>list item (click to delete)</button></li>
  <li><button>list item (click to delete)</button></li>
  <li><button>list item (click to delete)</button></li>
  <li><button>list item (click to delete)</button></li>
  <li><em>&hellip;More will be added after 3 seconds&hellip;</em></li>
</ol>

@Sebastien Lorber 2014-02-26 13:31:29

it seems to work pretty nicely for new DOM nodes. Can we adapt it to also handle dom node changes (at least the DOM node values/text?)

@vsync 2014-03-06 15:43:31

@SebastienLorber - who is "we"? you, as a programmer, can take this code and use it however you wish. just read on the MDN which things you can observe the DOM for and which you cannot.

@A1rPun 2014-06-27 23:21:50

Pass the mutations, observer parameters to the callback function for more control.

@stiller_leser 2014-11-26 11:25:51

This helped me a lot, but how do I "unbind" this? Say I want to watch for a change only once, but do this on multiple occasions? oberserveDOM = null obviously won't work...

@f0ster 2015-06-10 17:57:00

Why would this only work for appended/removed? It looks like the mutation events cover more than that.. developer.mozilla.org/en-US/docs/Web/Guide/Events/…

@Niloct 2018-09-25 21:41:32

This is a good start but failed to catch when are multiple mutations at once and so the childList mutation is not on mutations[0]. You should check every item of mutations.

@Anthony Awuley 2018-05-23 17:04:38

This is an example using MutationObserver from Mozilla adapted from this blog post

Chrome 18+, Firefox 14+, IE 11+, Safari 6+

// Select the node that will be observed for mutations
var targetNode = document.getElementById('some-id');

// Options for the observer (which mutations to observe)
var config = { attributes: true, childList: true };

// Callback function to execute when mutations are observed
var callback = function(mutationsList) {
    for(var mutation of mutationsList) {
        if (mutation.type == 'childList') {
            console.log('A child node has been added or removed.');
        }
        else if (mutation.type == 'attributes') {
            console.log('The ' + mutation.attributeName + ' attribute was modified.');
        }
    }
};

// Create an observer instance linked to the callback function
var observer = new MutationObserver(callback);

// Start observing the target node for configured mutations
observer.observe(targetNode, config);

// Later, you can stop observing
observer.disconnect();

@pie6k 2015-02-06 16:02:56

I have recently written a plugin that does exactly that - jquery.initialize

You use it the same way as .each function

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

The difference from .each is - it takes your selector, in this case .some-element and wait for new elements with this selector in the future, if such element will be added, it will be initialized too.

In our case initialize function just change element color to blue. So if we'll add new element (no matter if with ajax or even F12 inspector or anything) like:

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

Plugin will init it instantly. Also plugin makes sure one element is initialized only once. So if you add element, then .detach() it from body and then add it again, it will not be initialized again.

$("<div/>").addClass('some-element').appendTo("body").detach()
    .appendTo(".some-container");
//initialized only once

Plugin is based on MutationObserver - it will work on IE9 and 10 with dependencies as detailed on the readme page.

@thexpand 2018-06-25 08:36:19

Please, add to npm.

@StartCoding 2017-02-13 09:24:21

How about extending a jquery for this?

   (function () {
        var ev = new $.Event('remove'),
            orig = $.fn.remove;
        var evap = new $.Event('append'),
           origap = $.fn.append;
        $.fn.remove = function () {
            $(this).trigger(ev);
            return orig.apply(this, arguments);
        }
        $.fn.append = function () {
            $(this).trigger(evap);
            return origap.apply(this, arguments);
        }
    })();
    $(document).on('append', function (e) { /*write your logic here*/ });
    $(document).on('remove', function (e) { /*write your logic here*/ ) });

Jquery 1.9+ has built support for this(I have heard not tested).

@gblazex 2010-07-10 15:24:28

2015 update, new MutationObserver is supported by modern browsers:

Chrome 18+, Firefox 14+, IE 11+, Safari 6+

If you need to support older ones, you may try to fall back to other approaches like the ones mentioned in this 5 (!) year old answer below. There be dragons. Enjoy :)


Someone else is changing the document? Because if you have full control over the changes you just need to create your own domChanged API - with a function or custom event - and trigger/call it everywhere you modify things.

The DOM Level-2 has Mutation event types, but older version of IE don't support it. Note that the mutation events are deprecated in the DOM3 Events spec and have a performance penalty.

You can try to emulate mutation event with onpropertychange in IE (and fall back to the brute-force approach if non of them is available).

For a full domChange an interval could be an over-kill. Imagine that you need to store the current state of the whole document, and examine every element's every property to be the same.

Maybe if you're only interested in the elements and their order (as you mentioned in your question), a getElementsByTagName("*") can work. This will fire automatically if you add an element, remove an element, replace elements or change the structure of the document.

I wrote a proof of concept:

(function (window) {
    var last = +new Date();
    var delay = 100; // default delay

    // Manage event queue
    var stack = [];

    function callback() {
        var now = +new Date();
        if (now - last > delay) {
            for (var i = 0; i < stack.length; i++) {
                stack[i]();
            }
            last = now;
        }
    }

    // Public interface
    var onDomChange = function (fn, newdelay) {
        if (newdelay) delay = newdelay;
        stack.push(fn);
    };

    // Naive approach for compatibility
    function naive() {

        var last = document.getElementsByTagName('*');
        var lastlen = last.length;
        var timer = setTimeout(function check() {

            // get current state of the document
            var current = document.getElementsByTagName('*');
            var len = current.length;

            // if the length is different
            // it's fairly obvious
            if (len != lastlen) {
                // just make sure the loop finishes early
                last = [];
            }

            // go check every element in order
            for (var i = 0; i < len; i++) {
                if (current[i] !== last[i]) {
                    callback();
                    last = current;
                    lastlen = len;
                    break;
                }
            }

            // over, and over, and over again
            setTimeout(check, delay);

        }, delay);
    }

    //
    //  Check for mutation events support
    //

    var support = {};

    var el = document.documentElement;
    var remain = 3;

    // callback for the tests
    function decide() {
        if (support.DOMNodeInserted) {
            window.addEventListener("DOMContentLoaded", function () {
                if (support.DOMSubtreeModified) { // for FF 3+, Chrome
                    el.addEventListener('DOMSubtreeModified', callback, false);
                } else { // for FF 2, Safari, Opera 9.6+
                    el.addEventListener('DOMNodeInserted', callback, false);
                    el.addEventListener('DOMNodeRemoved', callback, false);
                }
            }, false);
        } else if (document.onpropertychange) { // for IE 5.5+
            document.onpropertychange = callback;
        } else { // fallback
            naive();
        }
    }

    // checks a particular event
    function test(event) {
        el.addEventListener(event, function fn() {
            support[event] = true;
            el.removeEventListener(event, fn, false);
            if (--remain === 0) decide();
        }, false);
    }

    // attach test events
    if (window.addEventListener) {
        test('DOMSubtreeModified');
        test('DOMNodeInserted');
        test('DOMNodeRemoved');
    } else {
        decide();
    }

    // do the dummy test
    var dummy = document.createElement("div");
    el.appendChild(dummy);
    el.removeChild(dummy);

    // expose
    window.onDomChange = onDomChange;
})(window);

Usage:

onDomChange(function(){ 
    alert("The Times They Are a-Changin'");
});

This works on IE 5.5+, FF 2+, Chrome, Safari 3+ and Opera 9.6+

@esafwan 2010-07-10 16:08:01

cant we add a active listener(dont knw wht to say!) or something that check in interval and check the dom?

@gblazex 2010-07-16 00:04:56

thoughtful comment from you as always :) Have you learned to use the this keyword btw?

@Kees C. Bakker 2011-01-22 16:43:17

Wondering: how does jQuery live() solve this problem if they can't detect a DOM change?

@Oscar Godson 2012-01-29 02:26:03

I can't believe that in 2010 you had IE5.5 to test this.

@Bojangles 2013-02-04 13:58:04

@JoshStodola The bold was annoying me too. I decided to fix it.

@pie6k 2015-02-06 17:05:46

Mutations events are deprecated. You should use MutationObserver. I've written my plugin for problems like this - github.com/AdamPietrasiak/jquery.initialize

@SuperUberDuper 2015-03-24 10:43:22

How can I get jquery onClick to fire before a mutation observer, that fires when a button is clicked with a ember action? stackoverflow.com/questions/29216434/…

@RobG 2015-11-01 23:35:47

BTW, passing window to window in (function(window){...}(window)) is pointless. If the intention is to get the global/window object safely, pass in this: (function(window){...}(this)) since in global code, this always points to the global/window object.

@RobG 2015-11-01 23:38:57

I can't see how the naïve function can work. getElementsByTagName returns a live node list, so last and current will always contain exactly the same elements. last should be static (e.g. use querySelectorAll instead). Perhaps that section of the code hasn't been tested? Also, even if converted to a static array, if the same number of nodes is added as removed, then the length test will exit early when there have been changes made.

@HB MAAM 2014-03-06 06:30:44

or you can simply Create your own event, that run everywhere

 $("body").on("domChanged", function () {
                //dom is changed 
            });


 $(".button").click(function () {

          //do some change
          $("button").append("<span>i am the new change</span>");

          //fire event
          $("body").trigger("domChanged");

        });

Full example http://jsfiddle.net/hbmaam/Mq7NX/

@lefoy 2016-11-16 20:20:17

this is not the same... the method described above is still valid api.jquery.com/trigger

Related Questions

Sponsored Content

75 Answered Questions

[SOLVED] What is the JavaScript version of sleep()?

  • 2009-06-04 14:41:10
  • fmsf
  • 2421496 View
  • 2122 Score
  • 75 Answer
  • Tags:   javascript sleep

75 Answered Questions

[SOLVED] How can I convert a string to boolean in JavaScript?

  • 2008-11-05 00:13:08
  • Kevin
  • 1816180 View
  • 2339 Score
  • 75 Answer
  • Tags:   javascript

38 Answered Questions

[SOLVED] How do I loop through or enumerate a JavaScript object?

12 Answered Questions

[SOLVED] Stop setInterval call in JavaScript

30 Answered Questions

[SOLVED] How to change an element's class with JavaScript?

  • 2008-10-12 20:06:43
  • Nathan Smith
  • 2483054 View
  • 2656 Score
  • 30 Answer
  • Tags:   javascript html dom

14 Answered Questions

[SOLVED] How to move an element into another element?

  • 2009-08-14 20:14:45
  • Mark Richman
  • 1054811 View
  • 1618 Score
  • 14 Answer
  • Tags:   javascript jquery html

42 Answered Questions

[SOLVED] Detecting an undefined object property

13 Answered Questions

[SOLVED] event.preventDefault() vs. return false

77 Answered Questions

[SOLVED] How do I detect a click outside an element?

  • 2008-09-30 13:17:12
  • Sergio del Amo
  • 1164708 View
  • 2376 Score
  • 77 Answer
  • Tags:   javascript jquery

29 Answered Questions

[SOLVED] jQuery scroll to element

  • 2011-07-13 09:49:44
  • DiegoP.
  • 2366308 View
  • 2202 Score
  • 29 Answer
  • Tags:   javascript jquery

Sponsored Content