I recently came across this awesome MutationObserver feature which sort of keep tracks of the changes on any dom element. I used the code that was shown on the mozilla developer network, but can't seem to make it run. This is the code I used (link):
// create an observer instance
var target = document.querySelector('#something');
console.log(target);
var observer = new WebKitMutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
console.log("Success");
//$('#log').text('input text changed: "' + target.text() + '"');
//console.log(mutation, mutation.type);
});
});
observer.observe(target, { attributes: true, childList: true, characterData: true });
//observer.disconnect(); - to stop observing
// test case
setInterval(function(){
document.querySelector('#something').innerHTML = Math.random();
},1000);
The above code doesn't seems to work. However if I modify the same code with a bit of jQuery, everything seems to work just fine (Demo here). Is there something I'm missing from the docs or I'm just misinterpreting the observer feature.
You need subtree: true
http://jsfiddle.net/6Jajs/1/
The inner text would normally be a child text() element in the DOM. Without the subtree it will only watch the element itself.
There is possible confusion surrounding "characterData" (https://developer.mozilla.org/en-US/docs/Web/API/CharacterData), but it seems that that applies only to nodes that directly contain text. The DOM is structured so that most markup elements contain mixed type which optionally include a child text node (which in turn would implement characterData, but would be a child of the targeted node).
Simple Example:
<div contentEditable id="myID">MUST EDIT NOW</div>
<script>
let x = new MutationObserver( function(){ alert('DETECTED'); } );
x.observe( myID , {subtree:true,characterData:true} );
</script>
See Example Live: https://jsfiddle.net/mbo9eLt5/
To Watch Text or Input changes
use
characterData: true
Example:
var target = document.querySelector('#text');
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
console.log(mutation.type);
});
});
var config = {
characterData: true,
subtree: true,
};
observer.observe(target, config);
// otherwise
observer.disconnect();
observer.observe(target, config);
<div id="text" contenteditable="true">characterData:true</div>
To Watch Child or Append Text or Inserting Dom
childList:true
Example:
var target = document.querySelector('#text');
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
console.log(mutation.type);
});
});
var config = {
childList: true,
subtree: true,
};
observer.observe(target, config);
// otherwise
observer.disconnect();
observer.observe(target, config);
<div id="text" contenteditable="true">characterData:true</div>
<button onclick="testappend();
function testappend(){
document.getElementById('text').append('tesxt')
}">append</button>
To Watch dom Attributes
attributes: true
Example
var target = document.querySelector('#text');
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
console.log(mutation.type);
});
});
var config = {
characterData: true,
attributes: true,
};
observer.observe(target, config);
// otherwise
observer.disconnect();
observer.observe(target, config);
<div id="text" contenteditable="true">characterData:true</div>
<button onclick="testappend();
function testappend(){
document.getElementById('text').classList.add('tesxt')
}">add class</button>
<button onclick="setat();
function setat(){
document.getElementById('text').setAttribute('data-prop','text')
}">set attribute</button>
attribute old value
https://developer.mozilla.org/en-US/docs/Web/API/MutationObserverInit/attributeOldValue
Related
I want to display a modal popup only after one of any DOM elements with the same class are updated. I am guessing MutationObserver is the best tool for this? Here is what I've got so far but it is not working...
// Get the modal
var modal = document.getElementById("save-search-modal");
// select the target nodes for mutation observing
var target = document.getElementsByClassName("sidx-pill-value")[0];
// When .sidx-pill-value is updated, open the modal
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
modal.style.display = "block";
});
});
// configuration of the observer:
var config = { subtree: true, characterData: true, attributes: true, childList: true }
// pass in the target node, as well as the observer options
observer.observe(target, config);
I want to detect changes in div-elements. I tried already the "addEventListener" with some types (e.g.: change, load).
Here is my example, but the event won't trigger:
<!doctype html>
<html>
<head>
</head>
<body>
<div id='DivElement'>Test Div Text...</div>
<button type="button" onclick="EditDivElement()">click me!</button>
<script>
function EditDivElement() {
ClickCounter++;
SelectedDiv.textContent="new text content: " + ClickCounter;
}
function OnDivEdit() {
alert("success!");
}
var ClickCounter = 0;
var SelectedDiv = document.querySelector("#DivElement");
SelectedDiv.addEventListener("change", OnDivEdit);
</script>
</body>
</html>
(Edit: It's for a browser-addon and it should be used on other websites.)
You can use a MutationObserver.
From MDN:
// select the target node
var target = document.querySelector('#some-id');
// create an observer instance
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
console.log(mutation.type);
});
});
// configuration of the observer:
var config = { attributes: true, childList: true, characterData: true };
// pass in the target node, as well as the observer options
observer.observe(target, config);
// later, you can stop observing
observer.disconnect();
Note: Deprecated
What I have done in the extensions I wrote was to listen on DOMNodeInserted.
div.addEventListener("DOMNodeInserted", function (e) {
e.target //
}, false);
I try to observe if the text in the HTML element is changing, so if certain text has appeared, it will display another element.
var travelarea = document.querySelector("#travelInformationStep");
var MutationObserver = window.MutationObserver;
var observer = new MutationObserver(function(mutations){
mutations.forEach(function(mutation){
if(mutation.target.text === "Europe"){
document.getElementById("terjer2").style.display = "block";
}
});
});
var config = {attributes: true, childList: true, characterData: true}
observer.observe(travelarea, config);
But I'm not sure is how should I check those changes in the if statement.
Try to check target's textContent. If the use case requires to avoid checking hidden or other text content that is not rendered in the browser, but is present in the mutation.target you can use innerText instead of textContent
var travelarea = document.querySelector("#travelInformationStep");
var MutationObserver = window.MutationObserver;
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if (mutation.target.textContent === "Europe") {
alert('Text changed to Europe');
}
});
});
var config = {attributes: true, childList: true, characterData: true};
observer.observe(travelarea, config);
document.getElementById('changeText').addEventListener('click', function(e) {
travelarea.textContent = travelarea.textContent === 'Europe' ? 'random text' : 'Europe';
});
<p id="travelInformationStep">Some text</p>
<button id="changeText">Change</button>
I have a simple MutationObserver setup as a test. The HTML has a span whose text content gets updated once per second (and a div for messages):
<span class="tester"></span>
<div id="msg"></div>
The MutationObserver is set to watch .tester and writes text to the #msg div when it observes a change. Meanwhile, a setInterval() runs once/second to change the text in .tester:
var target = document.querySelector('.tester');
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
console.log(mutation.type);
$('#msg').append(mutation.type+"<br/>")
setTimeout(function() { $('#msg').text(''); }, 500);
});
});
var config = { attributes: true, childList: true, characterData: true };
observer.observe(target, config);
setInterval(function() {
$('#msg').text('');
$('.tester').text("update: "+Math.random())
}, 1000);
I would expect this code to print once per second that the characterData has changed. According to Mozilla's docs for MutationObserver, it says about characterData: "Set to true if mutations to target's data are to be observed." Instead, I see no characterData mutations but do see two childList mutations every second.
Why am I not seeing any characterData mutations, and why am I seeing two childList mutations?
Here's a working example with CodePen.
The reason is as Jeremy Banks said: When you use jQuery's text(), it removes all the text nodes and then adds in new ones. That's not a change to character data, it's a change to the childList: Removing the node that's there and replacing it with a new one.
To see a change to character data, you have to modify the existing text node's nodeValue, and observe subtree modifications:
var target = document.querySelector('.tester');
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
console.log(mutation.type);
$('#msg').append(mutation.type+"<br/>")
setTimeout(function() { $('#msg').text(''); }, 500);
});
});
var config = {
attributes: true,
childList: true,
characterData: true,
subtree: true // <=== Change, added subtree
};
observer.observe(target, config);
var timer = setInterval(function() {
$('#msg').text('');
// Change --VVVVV modifying the existing child node
$('.tester')[0].firstChild.nodeValue = "updated" + Math.random();
}, 1000);
// Stop after 10 seconds
setTimeout(function() {
clearInterval(timer);
}, 10000);
<span class="tester">x</span><!-- Change, added a starting child node -->
<div id="msg"></div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
Re your question about why there are two childList mutations, yes I think you're right: They're removing the child, then adding a new one. If we use the replaceChild method, we see only a single mutation:
var target = document.querySelector('.tester');
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
console.log(mutation.type);
$('#msg').append(mutation.type+"<br/>")
setTimeout(function() { $('#msg').text(''); }, 500);
});
});
var config = {
attributes: true,
childList: true,
characterData: true,
subtree: true // <=== Change, added subtree
};
observer.observe(target, config);
var timer = setInterval(function() {
$('#msg').text('');
// Change --VVVVV modifying the existing child node
var text = document.createTextNode("updated" + Math.random());
var parent = $('.tester')[0];
parent.replaceChild(text, parent.firstChild);
}, 1000);
// Stop after 10 seconds
setTimeout(function() {
clearInterval(timer);
}, 10000);
<span class="tester">x</span><!-- Change, added a starting child node -->
<div id="msg"></div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
Is there a way to have a listener for when a div element is empty?
$('#myDiv').emptyEvent(function(){
)};
You should run David's code inside an event handler, such as DOMNodeInserted, DOMCharacterDataModified, or DOMSubtreeModified. The latter being the most recommended. For example:
$('#myDiv').bind("DOMSubtreeModified", function(){
if ( $('#myDiv').html() == "" ) {
}
)};
Edit: Such implementation is however deprecated, as stated in the comments. An alternative implementation, as suggested by david, is the following:
// select the target node
var target = $("#myDiv")[0];
// create an observer instance
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if($("#myDiv").html() == ""){
// Do something.
}
});
});
// configuration of the observer:
var config = { attributes: true, childList: true, characterData: true };
// pass in the target node, as well as the observer options
observer.observe(target, config);