50

HTML5 includes a concept of "mutation observers" to monitor changes to the browser's DOM.

Your observer callback will be passed data which looks a lot like DOM tree snippets. I don't know if they are exactly this or how they work really.

But when you are writing code to interact with a 3rd party site over which you have no control, say with a Greasemonkey script or Google Chrome user script, you have to inspect the tree of elements passed in to find which information is relevant to you.

This is much simpler with selectors, just like working with any DOM, than walking the tree manually, especially for cross-browser code.

Is there a way to use jQuery selectors with the data passed to HTML5 mutation observer callbacks?

hippietrail
  • 15,848
  • 18
  • 99
  • 158
  • Related questions: [Is there a jQuery DOM change listener?](http://stackoverflow.com/questions/2844565/is-there-a-jquery-dom-change-listener), [DOM Mutation event in JQuery or vanilla Javascript](http://stackoverflow.com/questions/7692730/dom-mutation-event-in-jquery-or-vanilla-javascript) – hippietrail Sep 26 '12 at 09:41
  • See also: [Can I use Jquery selectors on an HTML string that is not attached to the DOM?](http://stackoverflow.com/questions/4336674) – hippietrail Oct 07 '12 at 05:06
  • 1
    There's a **plugin** for that: https://github.com/kapetan/jquery-observe – vsync Mar 19 '17 at 11:55

4 Answers4

69

Yes, you can use jQuery selectors on data returned to mutation observer callbacks.

See this jsFiddle.

Suppose you had HTML like so and you set an observer, like so:

var targetNodes = $(".myclass");
var MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
var myObserver = new MutationObserver(mutationHandler);
var obsConfig = {
  childList: true,
  characterData: true,
  attributes: true,
  subtree: true
};

//--- Add a target node to the observer. Can only add one node at a time.
targetNodes.each(function() {
  myObserver.observe(this, obsConfig);
});

function mutationHandler(mutationRecords) {
  console.info("mutationHandler:");

  mutationRecords.forEach(function(mutation) {
    console.log(mutation.type);

    if (typeof mutation.removedNodes == "object") {
      var jq = $(mutation.removedNodes);
      console.log(jq);
      console.log(jq.is("span.myclass2"));
      console.log(jq.find("span"));
    }
  });
}
setTimeout(function() {  
  $(".myclass").html ("[censored!]");
},1000)  
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<span class="myclass"> 
  <span class="myclass2">My 
    <span class="boldly">vastly</span> improved
  </span> 
  text.
</span>

You'll note that we can jQuery on the mutation.removedNodes.


If you then run $(".myclass").html ("[censored!]"); from the console you will get this from Chrome and Firefox:

mutationHandler:
childList
jQuery(<TextNode textContent="\n ">, span.myclass2, <TextNode textContent="\n text.\n ">)
true
jQuery(span.boldly)

which shows that you can use normal jQuery selection methods on the returned node sets.

mplungjan
  • 169,008
  • 28
  • 173
  • 236
Brock Adams
  • 90,639
  • 22
  • 233
  • 295
  • 1
    Do you know if it's officially supported by jQuery, if it works in all browsers that jQuery supports and which support mutation observers? Could it be that it just happens to work in these browsers? It appears that could be the case since the behaviour seems to differ... I'm definitely going to play with it now anyway! (-: – hippietrail Sep 26 '12 at 08:07
  • 1
    I don't think it's a question of "official" support. Things like `removedNodes` and `addedNodes` [return lists of nodes](http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-mutationrecord-addednodes). jQuery works on such lists all day long, always has. ... The code works on Firefox and Chrome, and per W3C spec. It should work on any browser that supports such events. – Brock Adams Sep 26 '12 at 08:26
  • At the time I asked this question I had in the back of my mind some vague recollection about "snippets" being somehow different when they weren't attached to the document DOM in the old days and wasn't sure if any such difference would still pertain under jQuery. – hippietrail Dec 20 '12 at 00:35
  • Unfortunately the jsFiddle doesn't work any more, in FF 24.0 at least. It spews some warnings in the console and drops dead. I'm available to list the errors, if you can't replicate the problem. It does work in Chrome 30.0.something, though. – Bogdan Stăncescu Oct 18 '13 at 20:50
  • @Gutza, I just tested it in FF 24 and Chrome latest release. It still works and I see no errors. Retry from a clean FF profile or a different machine. – Brock Adams Oct 18 '13 at 22:56
7

I don't have any personal code snippets for this one, but I have three resources that may help:

Example from link #3 'jquery-mutation-summary' library:

// Use document to listen to all events on the page (you might want to be more specific)
var $observerSummaryRoot = $(document);

// Simplest callback, just logging to the console
function callback(summaries){
    console.log(summaries);
}

// Connect mutation-summary
$observerSummaryRoot.mutationSummary("connect", callback, [{ all: true }]);

// Do something to trigger mutationSummary
$("<a />", { href: "http://joelpurra.github.com/jquery-mutation-summary"}).text("Go to the jquery-mutation-summary website").appendTo("body");

// Disconnect when done listening
$observerSummaryRoot.mutationSummary("disconnect");
Vita Pluvia
  • 470
  • 1
  • 4
  • 10
  • Hmm I just tried to use the non-jQuery original version of [Mutation Summary](https://code.google.com/p/mutation-summary/) but find it doesn't work to well with pages not designed with user scripts in mind. It's good at watching changes to elements referenced by `id` or `class` but not so good when all the changes you care about are several elements deep or with generated `id`s and `className`s. – hippietrail Oct 07 '12 at 12:36
  • It seems to me that jQuery is just window dressing in this case. Am I missing something? – Michael Scheper Aug 18 '15 at 20:37
3

I was working on a very similar problem for a Stack Exchange script I'm working on, and I needed to be able to monitor the DOM for changes. The jQuery docs didn't have anything helpful, but I did discover that the DOMNodeInserted event works in Chrome:

document.addEventListener("DOMNodeInserted", function(event){
    var element = event.target;

    if (element.tagName == 'DIV') {
        if (element.id == 'seContainerInbox') {
            //alert($('#seContainerInbox').parent().get(0).tagName);
            trimStoredItems();
            $('#seTabInbox').click();
            //   var newCount = getNewCount();
            // if there are new inbox items, store them for later
            storeNewInboxItems();
            applyNewStyleToItems();
        }
    }
});

I'm not 100% sure if this works in Firefox as I haven't got that far yet in the development process. Hope this helps!

jamesmortensen
  • 33,636
  • 11
  • 99
  • 120
  • I'm using `WebKitMutationObserver()` which I think is new and supported and the old stuff is deprecated. I'm not sure if `DOMNodeInserted` is part of that "old stuff". `WebKitMutationObserver` passes you a mutations object which while I guess it isn't itself DOM is basically a packet of DOM references. I want to know if I can wrap it or some of those references with jQuery... – hippietrail Sep 26 '12 at 07:15
  • I haven't used this myself, but from the way you describe it, I suspect you'd probably have to access the RAW DOM elements themselves so you could pass them into a jQuery object like pieces from two puzzles. I guess it would really depend on the actual data types. Maybe look at what "typeof" gives you to see if it's something that could be easily wrapped. Hope this helps! – jamesmortensen Sep 26 '12 at 07:20
  • 7
    `DOMNodeInserted` is not part of mutation observers. It is the old, buggy, *deprecated*, mutation-event system that mutation observers replace! – Brock Adams Sep 26 '12 at 07:23
  • @BrockAdams - You should post an answer if you have one. I was actually planning on replacing this with something better in my own code once I came across it. – jamesmortensen Sep 26 '12 at 07:29
2

I know this is an old question but perhaps this can help others searching for alternative solutions. I recently learned about Mutation Observers and wanted to experiment with using them alongside jQuery. I came up with two possible approaches and turned them into plugins. The code is available here.

The first approach (jquery.observeWithEvents.js) uses jQuery's trigger() function to trigger either an insertNode or removeNode event which can be bound to via jQuery's on() function. The second approach (jquery.observeWithCallbacks.js) uses traditional callback parameters. Please take a look at the README for examples and usage.

Bart Jedrocha
  • 11,450
  • 5
  • 43
  • 53