Skip to content

Instantly share code, notes, and snippets.

@Warsaalk
Last active February 5, 2024 08:34

Revisions

  1. Warsaalk revised this gist Dec 13, 2018. 1 changed file with 152 additions and 130 deletions.
    282 changes: 152 additions & 130 deletions mutationlistener.js
    Original file line number Diff line number Diff line change
    @@ -1,139 +1,161 @@
    /**
    * @author Klaas Van Parys
    *
    * Listen to changes within a target and subscribe to those changes.
    *
    * @class Observer
    * @class Observer
    * @constructor
    *
    * @param {Node|String} targetArgument This is the single target you want to observe. It can be a selector or a Node
    * @param {Boolean} [asyncArgument=false] Set to "true" if the found elements should be processed asynchronously
    */
    function Observer (targetArgument, asyncArgument) {

    var target = "string" === typeof targetArgument ? document.querySelector(targetArgument) : targetArgument;

    var async = "boolean" === typeof asyncArgument ? asyncArgument : false;

    var idList = [], idCallback = [];
    var classList = [], classCallback = [];
    var nodeList = [], nodeParent = [], nodeCallback = [];

    var process = function (callback, mutation) {
    if (async) {
    setTimeout(function(callbackArg, mutationArg) {
    if (callbackArg) return function () { callbackArg.call(mutationArg) };
    }(callback, mutation), 0);
    } else {
    callback.call(mutation);
    }
    };

    var mutationCallback = function (mutations) {

    var idQueue = {}, classQueue = {}, nodeQueue = {};

    for (var i=mutations.length; i--;){
    for (var n=mutations[i].addedNodes.length; n--;) {
    if (mutations[i].addedNodes[n].nodeType === 1){ //Only observe elements
    for (var x=idList.length; x--;) {
    if (mutations[i].addedNodes[n].id == idList[x]) {
    idQueue[x] = mutations[i].addedNodes[n];
    }
    }
    for (var x=classList.length; x--;) {
    if (mutations[i].addedNodes[n].classList.contains(classList[x])) {
    classQueue[x] = mutations[i].addedNodes[n];
    }
    }
    for (var x=nodeList.length; x--;) {
    if (mutations[i].addedNodes[n].nodeName.toUpperCase() == nodeList[x].toUpperCase()) {
    if (mutations[i].target.classList.contains(nodeParent[x]) || mutations[i].target.id == nodeParent[x]) {
    nodeQueue[x] = mutations[i].addedNodes[n];
    }
    }
    }
    }
    }
    }
    //Process found ID elements
    for (var index in idQueue) {
    process(idCallback[index], idQueue[index]);
    }
    //Process found Class elements
    for (var index in classQueue) {
    process(classCallback[index], classQueue[index]);
    }
    //Process found nodes
    for (var index in nodeQueue) {
    process(nodeCallback[index], nodeQueue[index]);
    }

    };

    // Init MutationObserver
    var mutationObserver = new MutationObserver(mutationCallback);
    mutationObserver.observe(target, {childList: true, subtree: true});

    /**
    * Start listening to a specific ID
    *
    * @method listenToId
    * @param {String} id The html ID you want to listen to
    * @param {Function} callback A function that needs to be called when the element is found
    */
    this.listenToId = function (id, callback) {
    var check = document.getElementById(id);
    if (check) {
    process(callback, check);
    }

    idList.push(id);
    idCallback.push(callback);
    };

    /**
    * Start listening to a specific ClassName
    *
    * @method listenToClass
    * @param {String} className The html ClassName you want to listen to
    * @param {Function} callback A function that needs to be called when the element is found
    */
    this.listenToClass = function (className, callback) {
    var check = document.getElementsByClassName(className);
    if (check.length > 0) {
    for (var i=check.length; i--;) {
    process(callback, check[i]);
    }
    }

    classList.push(className);
    classCallback.push(callback);
    };

    /**
    * Start listening to a specific Node name
    *
    * @method listenToElement
    * @param {String} nodeName The html Node name you want to listen to
    * @param {String} parentName The html ID or ClassName of the nodeName its parent
    * @param {Function} callback A function that needs to be called when the element is found
    */
    this.listenToElement = function (nodeName, parentName, callback) {
    var checkClass = document.getElementsByClassName(parentName);
    if (checkClass.length > 0) {
    for (var i=checkClass.length; i--;) {
    if (checkClass[i].nodeName.toUpperCase() == nodeName.toUpperCase()) {
    process(callback, checkClass[i]);
    }
    }
    }
    checkId = document.getElementById(parentName);
    if (checkId && checkId.nodeName.toUpperCase() == nodeName.toUpperCase()) {
    process(callback, checkId);
    }

    nodeList.push(nodeName);
    nodeParent.push(parentName);
    nodeCallback.push(callback);
    };

    function Observer (targetArgument, asyncArgument)
    {
    this.supported = "MutationObserver" in window;

    var target = "string" === typeof targetArgument ? document.querySelector(targetArgument) : targetArgument;

    var async = "boolean" === typeof asyncArgument ? asyncArgument : false;

    var idList = [], idCallback = [];
    var classList = [], classCallback = [];
    var nodeList = [], nodeParent = [], nodeCallback = [];

    var process = function (callback, mutation)
    {
    if (async) {
    setTimeout(function(callbackArg, mutationArg) {
    if (callbackArg) return function () { callbackArg.call(mutationArg) };
    }(callback, mutation), 0);
    } else {
    callback.call(mutation);
    }
    };

    var mutationCallback = function (mutations)
    {
    var idQueue = {}, classQueue = {}, nodeQueue = {};

    for (var i=mutations.length; i--;) {
    for (var n=mutations[i].addedNodes.length; n--;) {
    if (mutations[i].addedNodes[n].nodeType === 1){ //Only observe elements
    for (var x=idList.length; x--;) {
    if (mutations[i].addedNodes[n].id === idList[x]) {
    idQueue[x] = mutations[i].addedNodes[n];
    }
    }
    for (var x=classList.length; x--;) {
    if (mutations[i].addedNodes[n].classList.contains(classList[x])) {
    if (classQueue[x] === void 0) {
    classQueue[x] = [];
    }
    classQueue[x].push(mutations[i].addedNodes[n]);
    }
    }
    for (var x=nodeList.length; x--;) {
    if (mutations[i].addedNodes[n].nodeName.toUpperCase() === nodeList[x].toUpperCase()) {
    if (mutations[i].target.classList.contains(nodeParent[x]) || mutations[i].target.id === nodeParent[x]) {
    if (nodeQueue[x] === void 0) {
    nodeQueue[x] = [];
    }
    nodeQueue[x].push(mutations[i].addedNodes[n]);
    }
    }
    }
    }
    }
    }

    //Process found ID elements
    for (var index in idQueue) {
    process(idCallback[index], idQueue[index]);
    }
    //Process found Class elements
    for (var index in classQueue) {
    process(classCallback[index], classQueue[index]);
    }
    //Process found nodes
    for (var index in nodeQueue) {
    process(nodeCallback[index], nodeQueue[index]);
    }

    };

    if (this.supported) {
    // Init MutationObserver
    var mutationObserver = new MutationObserver(mutationCallback);
    mutationObserver.observe(target, {childList: true, subtree: true});
    }

    /**
    * Start listening to a specific ID
    *
    * @method listenToId
    * @param {String} id The html ID you want to listen to
    * @param {Function} callback A function that needs to be called when the element is found
    */
    this.listenToId = function (id, callback)
    {
    if (this.supported === false) return false;

    var check = document.getElementById(id);
    if (check) {
    process(callback, check);
    }

    idList.push(id);
    idCallback.push(callback);
    };

    /**
    * Start listening to a specific ClassName
    *
    * @method listenToClass
    * @param {String} className The html ClassName you want to listen to
    * @param {Function} callback A function that needs to be called when the element is found
    */
    this.listenToClass = function (className, callback)
    {
    if (this.supported === false) return false;

    var check = document.getElementsByClassName(className);
    if (check.length > 0) {
    for (var i=check.length; i--;) {
    process(callback, check[i]);
    }
    }

    classList.push(className);
    classCallback.push(callback);
    };

    /**
    * Start listening to a specific Node name
    *
    * @method listenToElement
    * @param {String} nodeName The html Node name you want to listen to
    * @param {String} parentName The html ID or ClassName of the nodeName its parent
    * @param {Function} callback A function that needs to be called when the element is found
    */
    this.listenToElement = function (nodeName, parentName, callback)
    {
    if (this.supported === false) return false;

    var checkClass = document.getElementsByClassName(parentName);
    if (checkClass.length > 0) {
    for (var i=checkClass.length; i--;) {
    if (checkClass[i].nodeName.toUpperCase() === nodeName.toUpperCase()) {
    process(callback, checkClass[i]);
    }
    }
    }
    var checkId = document.getElementById(parentName);
    if (checkId && checkId.nodeName.toUpperCase() === nodeName.toUpperCase()) {
    process(callback, checkId);
    }

    nodeList.push(nodeName);
    nodeParent.push(parentName);
    nodeCallback.push(callback);
    };
    };
  2. Warsaalk revised this gist Jan 7, 2016. No changes.
  3. Warsaalk revised this gist Jan 7, 2016. No changes.
  4. Warsaalk created this gist Jan 7, 2016.
    35 changes: 35 additions & 0 deletions mutationlistener-test.html
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,35 @@
    <!doctype html>
    <html>
    <head>
    <title>Mutation Listener</title>
    <script src="mutationlistener.js"></script>
    </head>
    <body>
    <div id="test">
    Existing content
    </div>
    <script>
    var listener = new Observer(document);
    listener.listenToId('test', function () {
    console.log(this);
    });
    listener.listenToClass('test', function () {
    console.log(this);
    });
    listener.listenToElement('p', 'test', function () {
    console.log(this);
    });

    var testClass = document.createElement('div');
    testClass.classList.add('test');
    testClass.appendChild(document.createTextNode('Newly added div with ClassName "test"'));

    document.body.appendChild(testClass);

    var paragraph = document.createElement('p');
    paragraph.appendChild(document.createTextNode('Newly added paragraph'));

    document.getElementById('test').appendChild(paragraph);
    </script>
    </body>
    </html>
    139 changes: 139 additions & 0 deletions mutationlistener.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,139 @@
    /**
    * Listen to changes within a target and subscribe to those changes.
    *
    * @class Observer
    * @constructor
    *
    * @param {Node|String} targetArgument This is the single target you want to observe. It can be a selector or a Node
    * @param {Boolean} [asyncArgument=false] Set to "true" if the found elements should be processed asynchronously
    */
    function Observer (targetArgument, asyncArgument) {

    var target = "string" === typeof targetArgument ? document.querySelector(targetArgument) : targetArgument;

    var async = "boolean" === typeof asyncArgument ? asyncArgument : false;

    var idList = [], idCallback = [];
    var classList = [], classCallback = [];
    var nodeList = [], nodeParent = [], nodeCallback = [];

    var process = function (callback, mutation) {
    if (async) {
    setTimeout(function(callbackArg, mutationArg) {
    if (callbackArg) return function () { callbackArg.call(mutationArg) };
    }(callback, mutation), 0);
    } else {
    callback.call(mutation);
    }
    };

    var mutationCallback = function (mutations) {

    var idQueue = {}, classQueue = {}, nodeQueue = {};

    for (var i=mutations.length; i--;){
    for (var n=mutations[i].addedNodes.length; n--;) {
    if (mutations[i].addedNodes[n].nodeType === 1){ //Only observe elements
    for (var x=idList.length; x--;) {
    if (mutations[i].addedNodes[n].id == idList[x]) {
    idQueue[x] = mutations[i].addedNodes[n];
    }
    }
    for (var x=classList.length; x--;) {
    if (mutations[i].addedNodes[n].classList.contains(classList[x])) {
    classQueue[x] = mutations[i].addedNodes[n];
    }
    }
    for (var x=nodeList.length; x--;) {
    if (mutations[i].addedNodes[n].nodeName.toUpperCase() == nodeList[x].toUpperCase()) {
    if (mutations[i].target.classList.contains(nodeParent[x]) || mutations[i].target.id == nodeParent[x]) {
    nodeQueue[x] = mutations[i].addedNodes[n];
    }
    }
    }
    }
    }
    }
    //Process found ID elements
    for (var index in idQueue) {
    process(idCallback[index], idQueue[index]);
    }
    //Process found Class elements
    for (var index in classQueue) {
    process(classCallback[index], classQueue[index]);
    }
    //Process found nodes
    for (var index in nodeQueue) {
    process(nodeCallback[index], nodeQueue[index]);
    }

    };

    // Init MutationObserver
    var mutationObserver = new MutationObserver(mutationCallback);
    mutationObserver.observe(target, {childList: true, subtree: true});

    /**
    * Start listening to a specific ID
    *
    * @method listenToId
    * @param {String} id The html ID you want to listen to
    * @param {Function} callback A function that needs to be called when the element is found
    */
    this.listenToId = function (id, callback) {
    var check = document.getElementById(id);
    if (check) {
    process(callback, check);
    }

    idList.push(id);
    idCallback.push(callback);
    };

    /**
    * Start listening to a specific ClassName
    *
    * @method listenToClass
    * @param {String} className The html ClassName you want to listen to
    * @param {Function} callback A function that needs to be called when the element is found
    */
    this.listenToClass = function (className, callback) {
    var check = document.getElementsByClassName(className);
    if (check.length > 0) {
    for (var i=check.length; i--;) {
    process(callback, check[i]);
    }
    }

    classList.push(className);
    classCallback.push(callback);
    };

    /**
    * Start listening to a specific Node name
    *
    * @method listenToElement
    * @param {String} nodeName The html Node name you want to listen to
    * @param {String} parentName The html ID or ClassName of the nodeName its parent
    * @param {Function} callback A function that needs to be called when the element is found
    */
    this.listenToElement = function (nodeName, parentName, callback) {
    var checkClass = document.getElementsByClassName(parentName);
    if (checkClass.length > 0) {
    for (var i=checkClass.length; i--;) {
    if (checkClass[i].nodeName.toUpperCase() == nodeName.toUpperCase()) {
    process(callback, checkClass[i]);
    }
    }
    }
    checkId = document.getElementById(parentName);
    if (checkId && checkId.nodeName.toUpperCase() == nodeName.toUpperCase()) {
    process(callback, checkId);
    }

    nodeList.push(nodeName);
    nodeParent.push(parentName);
    nodeCallback.push(callback);
    };

    };