13

I enjoy running custom scripts on pages that I do not own or control. Many times these pages have dynamically created content that I would like to apply a function to.

Is this possible? If so, how can I do this? Ideally I am looking for something live jQuery's live method, except instead of binding an event like click it would be more like an event that happens when the element is loaded in the DOM. load event would work for some elements but I don't think for all...

For this question, assume that you cannot look at or change the code that is inserting the DOM nodes. I would like a technique that I could use in a userscript or bookmarklet that could be used across multiple unrelated sites.

Edit: I am looking for something to use on my invert colors bookmarklet: JavaScript: Invert color on all elements of a page

Community
  • 1
  • 1
Muhd
  • 24,305
  • 22
  • 61
  • 78
  • What kind of function are you looking to apply? You could listen for the `DOMNodeInsertedIntoDocument` event, but this is likely to apply to many inserted nodes you won't be interested in. – nrabinowitz Mar 19 '12 at 22:41
  • I am interested in every node actually. I am changing the colors of everything on the page. – Muhd Mar 19 '12 at 22:42

5 Answers5

15

Assuming you're running a browser like Firefox or Chrome, you could listen for the DOMNodeInserted event:

$(document).on('DOMNodeInserted', function(e) {
    $(e.target).css({ color : '#c00' });
});

$('body').append('<div>test</div>');​

Fiddle: http://jsfiddle.net/VeF6g/ (probably fails in IE)

Update: The event is deprecated. You should use a MutationObserver:

var observer = new MutationObserver(function(mutationList) {
    for (var mutation of mutationList) {
        for (var child of mutation.addedNodes) {
            child.style.color = '#c00';
        }
    }
});
observer.observe(document, {childList: true, subtree: true});

// ready? Then stop listening with
observer.disconnect();

More information here: https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver

Gerrit
  • 423
  • 5
  • 13
nrabinowitz
  • 55,314
  • 10
  • 149
  • 165
  • 1
    I'd just like to note for the sake of random readers: don't use the mutation events if you can avoid it. Details: http://lists.w3.org/Archives/Public/public-webapps/2011JulSep/0779.html – Nickolay Mar 20 '12 at 00:53
  • 1
    @Nickolay - thanks for the reference. I know it's a bad idea in general to use these, but it seems reasonable for the OP's use case (among other things, the discussion is about a user script, so he'll be the only one affected by slowness or crashes). – nrabinowitz Mar 20 '12 at 03:55
6

If you don't have access to a library like jQuery, here is the syntax in only-JavaScript :

document.addEventListener('DOMNodeInserted', nodeInsert)

function nodeInsert () {
  event.srcElement.style.color = '#ffffff'
}

I think it should work in most browsers.

Preview
  • 35,317
  • 10
  • 92
  • 112
3

Here is one nice way to handle the node insertion event. set animations for the element that you are going to add using css.

<style type="text/css">

/* set up the keyframes; remember to create prefixed keyframes too! */

@keyframes nodeInserted {  
from { opacity: 0.99; }
to { opacity: 1; }  
}

#parent > button {
animation-duration: 0.001s;
animation-name: nodeInserted;
}

</style>

#parent is the id for the div, in which i am going to append button's dynamically.

<script type="text/javascript">

    var insertListener = function(event){
        if (event.animationName == "nodeInserted") {
            // This is the debug for knowing our listener worked!
            // event.target is the new node!
            console.warn("Another node has been inserted! ", event, event.target);
        }
    }

    document.addEventListener("animationstart", insertListener, false); // standard + firefox
    document.addEventListener("MSAnimationStart", insertListener, false); // IE
    document.addEventListener("webkitAnimationStart", insertListener, false); // Chrome + Safari

    setInterval(function(){

        var btn = document.createElement("BUTTON");

        var t = document.createTextNode("CLICK ME");       // Create a text node

        btn.appendChild(t);         

        document.getElementById("parent").appendChild(btn);

    }, 2000);

</script>
NR Ganesh
  • 111
  • 5
  • I have implemented this method and programmed some responsive resizing of an `iframe` inside the `insertListener` method. The strange thing is that the custom resizing behavior occurs only after scrolling down to the `iframe`, not before. Any idea what might be going on here? – W.M. Feb 11 '17 at 17:21
2

This is rather difficult to accomplish, because there is no viable event for reacting to DOM changes. I would rather stick to event delegation instead.

But there are some events that you may find useful or interesting. On Mozilla Developer Network's list of DOM events you can see eg.:

  • DOMNodeInserted,
  • DOMNodeInsertedIntoDocument,
  • DOMNodeRemoved,
  • DOMElementNameChanged,

All of them however are marked as W3C drafts.

Tadeck
  • 132,510
  • 28
  • 152
  • 198
  • What browsers have these implemented? Ideally I would like something that works in Chrome. – Muhd Mar 19 '12 at 22:47
  • The 'draft' marker is an oversight on part of MDC editors. Those events were in the DOML2 Events as well, which is "recomendation". Fact is, most of these events work on everything but IE; but you should avoid using them, if you can, since they have major downsides (details: http://lists.w3.org/Archives/Public/public-webapps/2011JulSep/0779.html ) – Nickolay Mar 20 '12 at 00:51
1

A generic way to detect node insertion is to use the DOMNodeInserted mutation event.

I am interested in every node actually. I am changing the colors of everything on the page.

For this purpose, a better solution is to inject a dynamic stylesheet, sufficed with the !important flag. If you only want to change colors, I recommend the Stylish extension (Stylish for Chrome) instead of GreaseMonkey.

Rob W
  • 341,306
  • 83
  • 791
  • 678
  • Right, but the `!important` flag would destroy a lot of pages. I'm looking to create an intelligent script that works across a lot of different pages and looks good. – Muhd Mar 19 '12 at 22:46
  • @Muhd You can choose smart selectors to only select specific elements. Stylish supports domain selection using RegExes via the `@-moz-document regexp() ` - See also [`@document`](https://developer.mozilla.org/en/CSS/@document). – Rob W Mar 19 '12 at 22:48