I need to be notified when an element with class 'nav' is created as the document is loading. Googling I found MutationObservers and thought they would be perfect, but I can't seem to get it working.
// ==UserScript==
// @name ii-shortcuts
// @namespace
// @include *
// @version 1
// @run-at document-start
// ==/UserScript==
var observer = new MutationObserver(function(mutations)
{
mutations.forEach(function(mutation)
{
if (mutation.target.getAttribute('class') == 'nav')
GM_log('nav creation');
});
});
observer.observe(document, {subtree: true, attributes: true, attributeFilter: ['class']});
I also tried.
// ==UserScript==
// @name ii-shortcuts
// @namespace
// @include *
// @version 1
// @run-at document-start
// ==/UserScript==
var observer = new MutationObserver(function(mutations)
{
mutations.forEach(function(mutation)
{
if (mutation.addedNodes[0].getAttribute('class') == 'nav')
GM_log('nav creation');
});
});
observer.observe(document, {subtree: true, childList: true});
But in nether case was 'nav creation' log on page load. What am I missing?
I need to be notified when an element with class 'nav' is created as the document is loading. Googling I found MutationObservers and thought they would be perfect, but I can't seem to get it working.
// ==UserScript==
// @name ii-shortcuts
// @namespace https://github.com/RedHatter
// @include *
// @version 1
// @run-at document-start
// ==/UserScript==
var observer = new MutationObserver(function(mutations)
{
mutations.forEach(function(mutation)
{
if (mutation.target.getAttribute('class') == 'nav')
GM_log('nav creation');
});
});
observer.observe(document, {subtree: true, attributes: true, attributeFilter: ['class']});
I also tried.
// ==UserScript==
// @name ii-shortcuts
// @namespace https://github.com/RedHatter
// @include *
// @version 1
// @run-at document-start
// ==/UserScript==
var observer = new MutationObserver(function(mutations)
{
mutations.forEach(function(mutation)
{
if (mutation.addedNodes[0].getAttribute('class') == 'nav')
GM_log('nav creation');
});
});
observer.observe(document, {subtree: true, childList: true});
But in nether case was 'nav creation' log on page load. What am I missing?
Share Improve this question asked Jul 13, 2013 at 17:20 AvaAva 2,1326 gold badges24 silver badges47 bronze badges 3- @CrazyTrain Not accouding to the specs "DOM mutation events must not fire for changes caused by the UA parsing the document ... However, mutation observers do fire, as required by the DOM specification." – Ava Commented Jul 13, 2013 at 19:23
- you can use arrive.js, it provides nice simple api to listen for elements creation (uses MutationObserver internally) – Uzair Farooq Commented Apr 19, 2014 at 8:27
- Hmm ... look very nice. Thank you @UzairFarooq – Ava Commented Apr 20, 2014 at 15:30
2 Answers
Reset to default 13Several issues (big to small):
When the document is first, statically loaded; the events are
childList
events, notattributes
events.For example,
$("body").append ('<p id="foo" class="bar">Hiya!</p><p>blah</p>');
generates one
childList
event, while a subsequent$("#foo").attr ("class", "bar2");
generates an
attributes
event.
The odds that
mutation.addedNodes[0]
contains an element with classnav
are practically zero. This is almost always a text node.
You need to check the whole array, PLUS thetarget
.Don't use
getAttribute('class') == 'nav'
to check for classes. This will throw exceptions for nodes without thegetAttribute
function and it will miss elements that have more than one class. EG:<p class="foo nav bar">...
Use
classList.contains()
on appropriate node types.Use a
@grant
directive if you use anyGM_
functions likeGM_log()
. Use a grant anyway, to ensure that the sandbox stays on.Avoid the use of
// @include *
. Especially with timers and observers, this can bog down your browser and your machine.This info is for Firefox. Chrome has big differences in how it implements Mutation observers. This kind of code will not work in Chrome before page load.
Putting it all together, the script becomes:
// ==UserScript==
// @name _ii-shortcuts
// @namespace https://github.com/RedHatter
// @include http://YOUR_SERVER.COM/YOUR_PATH/*
// @run-at document-start
// @version 1
// @grant GM_log
// ==/UserScript==
/*- The @grant directive is needed to work around a design change
introduced in GM 1.0. It restores the sandbox.
*/
var MutationObserver = window.MutationObserver;
var myObserver = new MutationObserver (mutationHandler);
var obsConfig = {
childList: true, attributes: true,
subtree: true, attributeFilter: ['class']
};
myObserver.observe (document, obsConfig);
function mutationHandler (mutationRecords) {
mutationRecords.forEach ( function (mutation) {
if ( mutation.type == "childList"
&& typeof mutation.addedNodes == "object"
&& mutation.addedNodes.length
) {
for (var J = 0, L = mutation.addedNodes.length; J < L; ++J) {
checkForCSS_Class (mutation.addedNodes[J], "nav");
}
}
else if (mutation.type == "attributes") {
checkForCSS_Class (mutation.target, "nav");
}
} );
}
function checkForCSS_Class (node, className) {
//-- Only process element nodes
if (node.nodeType === 1) {
if (node.classList.contains (className) ) {
console.log (
'New node with class "' + className + '" = ', node
);
// YOUR CODE HERE
//GM_log ('nav creation');
}
}
}
MutationObservers are called for more things than just adding nodes, including changes to attributes and removing nodes.
So watch out for mutation.addedNodes
returning null - in which case this code will fail. Try:
if (mutation.addedNodes && mutation.addedNodes[0].getAttribute('class') === 'nav') {
...
The mutation object also has a 'type' attribute that you can use to get more info; have you read the API docs on MDN? There are some good examples there.