Using NodeIterator (Or TreeWalker) with MutationObservers - javascript

I'm using mutation observers to monitor changes to the DOM, and I have a NodeIterator that replaces certain text nodes containing text filtered by a regular expression with dynamically created content.
The issue I'm having is that upon the first run through the NodeIterator, the Node Iterator is now pointing at null and it can't run the algorithm against the dynamically inserted content. If I try to reassign it, it hangs up. Why is it hanging up? Is there a solution to this problem?
var nodeIterator = document.createNodeIterator(
document.body,
NodeFilter.SHOW_TEXT,
acceptAlgo,
false
);
iterate(nodeIterator);
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if (mutation.addedNodes) {
var dynamicNode = document.createNodeIterator(
mutation.target,
NodeFilter.SHOW_TEXT,
acceptAlgo,
false
);
iterate(dynamicNode);
}
});
});
function iterate(iterator) {
while ((node = iterator.nextNode())) {
replaceFunction(node);
}
}
var config = {
attributes: false,
childList: true,
characterData: false,
subtree: true
};
observer.observe(document.body, config);

Related

How to monitor multiple node insertions at different levels in dom using MutationObserver

I have a parent node in my dom. I have to call a method when a specific element is inserted in the parent node (newly inserted node is not a direct child, its a subtree, may be present at 2/3 level deep).
I have a code for single element insertion which works as expected
var parentNode = jQuery('.parent-node');
var observer = new MutationObserver(function (mutations) {
if (parentNode.find('.sub-class-1')[0] !== undefined) {
//call method
this.disconnect();
}
}).observe(parentNode[0], {childList:true, subtree: true });
But i want to execute callbacks for multiple sub elements creations. i tried this by using forEach on sub-class elements array.
arr.forEach(function (ob) {
new MutationObserver(function (mutations) {
if (parentNode.find(ob.className)[0] != undefined) {
//ob.callback()
this.disconnect();
}
}).observe(parentNode[0], { attributes:false, childList: true, subtree: true });
});
But after processing one subclass, it does not processes the rest subclasses. I guess we can only create on object on particular node.
So, how to detect all this nodes on creation to execute specific callbacks using MutationObserver or is there any better alternative approach.
Hi please try this,
var config = { attributes: true, childList: true, characterData: true };
var observers = [];
function createObserver(target, index) {
var observer = new MutationObserver(function(mutations) {
var $this = this;
mutations.forEach(function(mutation) {
if (parentNode.find('.sub-class-1')[0] !== undefined) {
$this.disconnect();
}else{
observers.push($this);
}
});
});
observer.observe(target, config);
}
$('.parent-node').each(function(i, el) {
createObserver(el, i)
});

Find class of newly appear DOM using mutation observer?

I want to detect what kind of item is coming after facebook's new post appear on users' feed when they scroll, I'm able to detect the new dom change but I'm stuck at mutation observer callback,
The $(this) isn't equal to the newly appear DOM, what I don't know what to do.
// Necessary for Facebooks "neverending" scrolling
var observer = new MutationObserver(function (mutations, observer) {
// fired when a mutation occurs
var x = $(this);
console.log(x) // I'm stuck here, what to do?
});
// pass in the target node, as well as the observer options
observer.observe($('[id^=topnews_main_stream_]').get(0), {
attributes: true,
childList: true,
characterData: true,
subtree: true
});
You should log variable mutations in callback function:
var observer = new MutationObserver(function (mutations, observer) {
mutations.forEach(function(mutation) {
console.log(mutation);
});
});
You can use mutations object to filter the type of mutation and its addedNodes property will return the newly add nodes
var observer = new MutationObserver(function (mutations, observer) {
mutations.forEach(function(mutation) {
if (mutation.type == "childList") {
console.log(mutation.addedNodes)
}
});
});
var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver;
var list = document.querySelector('#form');
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if (mutation.type === 'childList') {
console.log(mutation.addedNodes)
}
});
});
observer.observe(list, {
attributes: true,
childList: true,
characterData: true
});
setTimeout(function() {
var data = 'Publishing options';
jQuery("#form").append(data)
}, 5000)
<script src="https://ajax.aspnetcdn.com/ajax/jQuery/jquery-3.1.1.min.js"></script>
<form action="" method="post" id="form">
</form>

MutationObserver — only do something if nodes added, not removed

I have a MutationObserver that I'm using like so—
var config = {
attributes: false,
childList: true,
characterData: false,
subtree: true
};
var observer = new MutationObserver(function(mutations) {
//need to call function1() only when nodes are ADDED, not removed
});
var startObserving = function () {
target = document.getElementsByClassName("message-container")[0];
observer.observe(target, config);
}
I need to both add and remove elements to/from the container that the MutationObserver is watching, but I only want to execute function1() when nodes are added. Is there a way to do this? I've been reading the MDN article but can't think of a way to do this. Any help would be appreciated!
You should be able to check the addedNodes property on each of the mutations object to determine if elements were added. You’ll probably also want to validate that the type is childList.
Check out the MutationRecord page on the MDN.
Something like
var observer = new MutationObserver(function(mutations) {
var hasUpdates = false;
for (var index = 0; index < mutations.length; index++) {
var mutation = mutations[index];
if (mutation.type === 'childList' && mutation.addedNodes.length) {
hasUpdates = true;
break;
}
}
if (hasUpdates) {
function1();
}
});
I prefer do this using observer options childList and subtree. Then observer filtering changes
const observer = new MutationObserver((mutations) => {
// do something
});
observer.observe(node, {
childList: true, // detecting childList changes
subtree: true // detecing in childs
});

simple MutationObserver version of DOMNodeRemovedFromDocument

I attach some functionality to DOM elements and want to be able to clear all references when the element is removed from the DOM so it can be garbage collected,
My initial version to detect the removel of an element was this:
var onremove = function(element, callback) {
var destroy = function() {
callback();
element.removeEventListener('DOMNodeRemovedFromDocument', destroy);
};
element.addEventListener('DOMNodeRemovedFromDocument', destroy);
};
Then I read that mutation events were deprecated in favor of MutationObserver. So I tried to port my code. This is what I came up with:
var isDescendant = function(desc, root) {
return !!desc && (desc === root || isDecendant(desc.parentNode, root));
};
var onremove = function(element, callback) {
var observer = new MutationObserver(function(mutations) {
_.forEach(mutations, function(mutation) {
_.forEach(mutation.removedNodes, function(removed) {
if (isDescendant(element, removed)) {
callback();
// allow garbage collection
observer.disconnect();
observer = undefined;
}
});
});
});
observer.observe(document, {
childList: true,
subtree: true
});
};
This looks overly complicated to me (and not very efficient). Am I missing something or is this really the way this is supposed to work?
Actually... yes, there is a more elegant solution :).
What you added looks good and seems to be well optimized. However there is an easier way to know if the node is attached to the DOM or not.
function onRemove(element, onDetachCallback) {
const observer = new MutationObserver(function () {
function isDetached(el) {
if (el.parentNode === document) {
return false;
} else if (el.parentNode === null) {
return true;
} else {
return isDetached(el.parentNode);
}
}
if (isDetached(element)) {
observer.disconnect();
onDetachCallback();
}
})
observer.observe(document, {
childList: true,
subtree: true
});
}
I believe recursion is unnecessary to solve this problem, and that .contains() on the removednodes list is faster then traversing the entire dom for every arbitrary removal. Alternatively, you can define a custom event and apply that to any element you want to watch for the removal of, here's an example using a mutation observer to create a custom "removed" event.
const removedEvent = new Event("removed");
const observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation){
mutation.removedNodes.forEach(function(node) {
node.dispatchEvent(removedEvent);
});
});
});
observer.observe(document.documentElement {
childList: true,
subtree: true,
});
// Usage example:
let element = document.getElementById("anyelement");
// The custom event "removed" listener can be added before or after the element is added to the dom
element.addEventListener("removed", function() {
// Do whatever you need for cleaning up but don't disconnect the observer if you have other elements you need to react to the removal of.
console.info("Element removed from DOM.");
});

How to listen for changes to the title element?

In Javascript, is there a technique to listen for changes to the title element?
5 years later we finally have a better solution. Use MutationObserver!
In short:
new MutationObserver(function(mutations) {
console.log(mutations[0].target.nodeValue);
}).observe(
document.querySelector('title'),
{ subtree: true, characterData: true, childList: true }
);
With comments:
// select the target node
var target = document.querySelector('title');
// create an observer instance
var observer = new MutationObserver(function(mutations) {
// We need only first event and only new value of the title
console.log(mutations[0].target.nodeValue);
});
// configuration of the observer:
var config = { subtree: true, characterData: true, childList: true };
// pass in the target node, as well as the observer options
observer.observe(target, config);
Also Mutation Observer has awesome browser support:
2022 Update
Mutation Observers are unequivocally the way to go now (see Vladimir Starkov's answer), with no need for fallbacks to the older APIs mentioned below. Furthermore, DOMSubtreeModified should be actively avoided now.
I'm leaving the remainder of this answer here for posterity.
2010 Answer
You can do this with events in most modern browsers (notable exceptions being all versions of Opera and Firefox 2.0 and earlier). In IE you can use the propertychange event of document and in recent Mozilla and WebKit browsers you can use the generic DOMSubtreeModified event. For other browsers, you will have to fall back to polling document.title.
Note that I haven't been able to test this in all browsers, so you should test this carefully before using it.
2015 Update
Mutation Observers are the way to go in most browsers these days. See Vladimir Starkov's answer for an example. You may well want some of the following as fallback for older browsers such as IE <= 10 and older Android browsers.
function titleModified() {
window.alert("Title modifed");
}
window.onload = function() {
var titleEl = document.getElementsByTagName("title")[0];
var docEl = document.documentElement;
if (docEl && docEl.addEventListener) {
docEl.addEventListener("DOMSubtreeModified", function(evt) {
var t = evt.target;
if (t === titleEl || (t.parentNode && t.parentNode === titleEl)) {
titleModified();
}
}, false);
} else {
document.onpropertychange = function() {
if (window.event.propertyName == "title") {
titleModified();
}
};
}
};
There's not a built-in event. However, you could use setInterval to accomplish this:
var oldTitle = document.title;
window.setInterval(function()
{
if (document.title !== oldTitle)
{
//title has changed - do something
}
oldTitle = document.title;
}, 100); //check every 100ms
This's my way, in a closure and check in startup
(function () {
var lastTitle = undefined;
function checkTitle() {
if (lastTitle != document.title) {
NotifyTitleChanged(document.title); // your implement
lastTitle = document.title;
}
setTimeout(checkTitle, 100);
};
checkTitle();
})();
Don't forget to remove listener when not needed anymore.
Vanilla JS:
const observer = new MutationObserver((mutations) => {
console.log(mutations[0].target.text);
});
observer.observe(document.querySelector("title"), {
subtree: true,
characterData: true,
childList: true,
})
observer.disconnect() // stops looking for changes
Or if you use React which is really neat with removing listeners, I wrote this hook:
React.useEffect(() => {
const observer = new MutationObserver(mutations => {
console.log(mutations[0].target.text)
})
observer.observe(document.querySelector("title"), {
subtree: true,
characterData: true,
childList: true,
})
return () => observer.disconnect()
}, [defaultTitle, notificationTitle])
const observer = new MutationObserver(([{ target }]) =>
// Log change
console.log(target.text),
)
observer.observe(document.querySelector('title'), {
childList: true,
})

Categories

Resources