Jquery: How to Listen for Dom Changes

Is there a JavaScript / jQuery DOM change listener?

For a long time, DOM3 mutation events were the best available solution, but they have been deprecated for performance reasons. DOM4 Mutation Observers are the replacement for deprecated DOM3 mutation events. They are currently implemented in modern browsers as MutationObserver (or as the vendor-prefixed WebKitMutationObserver in old versions of Chrome):

MutationObserver = window.MutationObserver || window.WebKitMutationObserver;

var observer = new MutationObserver(function(mutations, observer) {
// fired when a mutation occurs
console.log(mutations, observer);
// ...
});

// define what element should be observed by the observer
// and what types of mutations trigger the callback
observer.observe(document, {
subtree: true,
attributes: true
//...
});

This example listens for DOM changes on document and its entire subtree, and it will fire on changes to element attributes as well as structural changes. The draft spec has a full list of valid mutation listener properties:

childList

  • Set to true if mutations to target's children are to be observed.

attributes

  • Set to true if mutations to target's attributes are to be observed.

characterData

  • Set to true if mutations to target's data are to be observed.

subtree

  • Set to true if mutations to not just target, but also target's descendants are to be observed.

attributeOldValue

  • Set to true if attributes is set to true and target's attribute value before the mutation needs to be recorded.

characterDataOldValue

  • Set to true if characterData is set to true and target's data before the mutation needs to be recorded.

attributeFilter

  • Set to a list of attribute local names (without namespace) if not all attribute mutations need to be observed.

(This list is current as of April 2014; you may check the specification for any changes.)

jQuery: How to listen for DOM changes?

My best guess is that you want to listen to DOM mutation events.
You can do that by DOM mutation event as any normal javascript event such as a mouse click.

Refer to this : W3 MutationEvent

Example:

$("element-root").bind("DOMSubtreeModified", "CustomHandler");

jQuery watch for domElement changes?

The most effective way I've found is to bind to the DOMSubtreeModified event. It works well with both jQuery's $.html() and via standard JavaScript's innerHTML property.

$('#content').bind('DOMSubtreeModified', function(e) {
if (e.target.innerHTML.length > 0) {
// Content change handler
}
});

http://jsfiddle.net/hnCxK/

When called from jQuery's $.html(), I found the event fires twice: once to clear existing contents and once to set it. A quick .length-check will work in simple implementations.

It's also important to note that the event will always fire when set to an HTML string (ie '<p>Hello, world</p>'). And that the event will only fire when changed for plain-text strings.

Detect changes in the DOM

2015 update, new MutationObserver is supported by modern browsers:

Chrome 18+, Firefox 14+, IE 11+, Safari 6+

If you need to support older ones, you may try to fall back to other approaches like the ones mentioned in this 5 (!) year old answer below. There be dragons. Enjoy :)


Someone else is changing the document? Because if you have full control over the changes you just need to create your own domChanged API - with a function or custom event - and trigger/call it everywhere you modify things.

The DOM Level-2 has Mutation event types, but older version of IE don't support it. Note that the mutation events are deprecated in the DOM3 Events spec and have a performance penalty.

You can try to emulate mutation event with onpropertychange in IE (and fall back to the brute-force approach if non of them is available).

For a full domChange an interval could be an over-kill. Imagine that you need to store the current state of the whole document, and examine every element's every property to be the same.

Maybe if you're only interested in the elements and their order (as you mentioned in your question), a getElementsByTagName("*") can work. This will fire automatically if you add an element, remove an element, replace elements or change the structure of the document.

I wrote a proof of concept:

(function (window) {
var last = +new Date();
var delay = 100; // default delay

// Manage event queue
var stack = [];

function callback() {
var now = +new Date();
if (now - last > delay) {
for (var i = 0; i < stack.length; i++) {
stack[i]();
}
last = now;
}
}

// Public interface
var onDomChange = function (fn, newdelay) {
if (newdelay) delay = newdelay;
stack.push(fn);
};

// Naive approach for compatibility
function naive() {

var last = document.getElementsByTagName('*');
var lastlen = last.length;
var timer = setTimeout(function check() {

// get current state of the document
var current = document.getElementsByTagName('*');
var len = current.length;

// if the length is different
// it's fairly obvious
if (len != lastlen) {
// just make sure the loop finishes early
last = [];
}

// go check every element in order
for (var i = 0; i < len; i++) {
if (current[i] !== last[i]) {
callback();
last = current;
lastlen = len;
break;
}
}

// over, and over, and over again
setTimeout(check, delay);

}, delay);
}

//
// Check for mutation events support
//

var support = {};

var el = document.documentElement;
var remain = 3;

// callback for the tests
function decide() {
if (support.DOMNodeInserted) {
window.addEventListener("DOMContentLoaded", function () {
if (support.DOMSubtreeModified) { // for FF 3+, Chrome
el.addEventListener('DOMSubtreeModified', callback, false);
} else { // for FF 2, Safari, Opera 9.6+
el.addEventListener('DOMNodeInserted', callback, false);
el.addEventListener('DOMNodeRemoved', callback, false);
}
}, false);
} else if (document.onpropertychange) { // for IE 5.5+
document.onpropertychange = callback;
} else { // fallback
naive();
}
}

// checks a particular event
function test(event) {
el.addEventListener(event, function fn() {
support[event] = true;
el.removeEventListener(event, fn, false);
if (--remain === 0) decide();
}, false);
}

// attach test events
if (window.addEventListener) {
test('DOMSubtreeModified');
test('DOMNodeInserted');
test('DOMNodeRemoved');
} else {
decide();
}

// do the dummy test
var dummy = document.createElement("div");
el.appendChild(dummy);
el.removeChild(dummy);

// expose
window.onDomChange = onDomChange;
})(window);

Usage:

onDomChange(function(){ 
alert("The Times They Are a-Changin'");
});

This works on IE 5.5+, FF 2+, Chrome, Safari 3+ and Opera 9.6+

How do I call a function every time the DOM changes in jQuery?

Taking your question in the strictest sense, something like this:

//--- Narrow the container down AMAP.
$("YOUR SELECTOR").bind ("DOMSubtreeModified", HandleDOM_ChangeWithDelay);

var zGbl_DOM_ChangeTimer = null;

function HandleDOM_ChangeWithDelay (zEvent) {
if (typeof zGbl_DOM_ChangeTimer == "number") {
clearTimeout (zGbl_DOM_ChangeTimer);
zGbl_DOM_ChangeTimer = '';
}
zGbl_DOM_ChangeTimer = setTimeout (HandleDOM_Change, 333);
}

function HandleDOM_Change () {
// YOUR CODE HERE.
}

Note that you want the intermediate delay function because the changes will come in clusters of 10 to 100's of events on a site like Youtube or Google.

You only want to fire on the last event of a cluster -- that's when the change you care about is finished.



IMPORTANT:

It's been a while since I've used the DOMSubtreeModified approach, because it performs poorly in practice. So, I forgot I had a utility function for it.

Also, as Raynos reminds, mutation events are deprecated. So, Firefox may stop supporting these events in some future release.

One other problem: If your script also changes the nodes in the container you are monitoring, the script can get stuck in an infinite loop/recursion.

Here is the code to avoid the loop (and that global variable):

function HandleDOM_Change () {
// YOUR CODE HERE.
}

//--- Narrow the container down AMAP.
fireOnDomChange ('YOUR JQUERY SELECTOR', HandleDOM_Change, 100);

function fireOnDomChange (selector, actionFunction, delay)
{
$(selector).bind ('DOMSubtreeModified', fireOnDelay);

function fireOnDelay () {
if (typeof this.Timer == "number") {
clearTimeout (this.Timer);
}
this.Timer = setTimeout ( function() { fireActionFunction (); },
delay ? delay : 333
);
}

function fireActionFunction () {
$(selector).unbind ('DOMSubtreeModified', fireOnDelay);
actionFunction ();
$(selector).bind ('DOMSubtreeModified', fireOnDelay);
}
}

Detect element content changes with jQuery

These are mutation events.

I have not used mutation event APIs in jQuery, but a cursory search led me to this project on GitHub. I am unaware of the project's maturity.

Monitoring DOM Changes in JQuery

After further researching this issue, I stumbled upon a blog post by Rick Strahl in which we demonstrates a jQuery plug-in that allows for monitoring of CSS properties: jQuery CSS Property Monitoring Plug-in updated



Related Topics



Leave a reply



Submit