Skip to content

Instantly share code, notes, and snippets.

@Warsaalk
Last active February 5, 2024 08:34
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Warsaalk/17346faac439f7d8a30a to your computer and use it in GitHub Desktop.
Save Warsaalk/17346faac439f7d8a30a to your computer and use it in GitHub Desktop.
A Javascript class that observes DOM changes for a single target. It allows listening to ID's, Classes and Node names and returns the nodes if they are added to the DOM.
<!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>
/**
* @author Klaas Van Parys
*
* 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)
{
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);
};
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment