Javascript 'div empty' event listener - javascript

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);

Related

Display Modal on DOM Element Update with MutationObserver

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);

MutationObserver not firing when setting display to none

I have a node called 'cover' that gets set to visible or not visible when the ajax layer wants to hide/show it based on it being our veil. But we want our veil to be more than just a single node going visible or invisible. So I wrote a MutationObserver to watch for the change and do the extra work. This works fine when the node gets changed to display: block. It does NOT fire when it changes to display: none.
You can see the observer below, and between this and breakpoints, I am confident that it is never called on display:none changes. And yes, I can see that change has been made in the watch list. This happens in both IE and Chrome.
Is this expected? I didn't expect it. But if so, how can I get that display:none event?
The call to start the observer:
veilObserver.observe(cover, { attributes: true, childList: false, subtree: false });
The observer:
const veilObserver = new MutationObserver(function(mutationsList, observer) {
console.log("MutationObserver enter");
var cover = document.getElementById('cover');
if(cover) {
console.log("MutationObserver cover");
if(cover.style.display == 'none') {
console.log("MutationObserver closing");
closeVeil();
} else if(cover.style.display == 'block') {
openVeil();
} else {
//this should never happen, but if it does, we want to make sure the veil is closed because we don't know whether it should be open or
//closed and I'd rather default to open so the user isn't locked forever.
console.log('Mutation!!! but display not recognized: ' + cover.style.display);
closeVeil();
}
} else {
console.log("MutationObserver disconnecting");
//this implies the page lacks the required HTML. Disconnect the observer and don't both them again.
veilObserver.disconnect();
}
});
If your having trouble determining which parent to observe for attribute changes, you can observe all attribute changes on the document, filter irrelevant changes as much as possible, and then check if your element is visible.
var observer;
observer = new MutationObserver(function(mutations) {
let cover = document.getElementById('cover')
if (!cover) observer.disconnect()
if (!isVisible(cover)) console.log("closing");
else console.log("opening")
});
// main diffrence is the target node to listen to is now document.body
observer.observe(document.body, {
attributes: true,
attributeFilter: ['style'],
subtree: true
});
function isVisible(element) { return (element.offsetWidth > 0 && element.offsetHeight > 0) }
Copied this from the docs and tailored it to your code.
You should try observing the parent element of #cover. That way any mutations inside of that element will be observed.
// Select the node that will be observed for mutations
const targetNode = document.getElementById(/* The parent element of #cover */);
// Any changes inside this div will be observed.
// Options for the observer (which mutations to observe)
const config = { attributes: true, childList: true, subtree: true };
// Callback function to execute when mutations are observed
const callback = function(mutationsList, observer) {
// Use traditional 'for loops' for IE 11
for(let mutation of mutationsList) {
if (mutation.type === 'childList') {
console.log('A child node has been added or removed.');
// Check if the element that changed was #cover
console.log(mutation.target.id === 'cover');
if(mutation.target.id === 'cover') {
let id = mutation.target.id;
if(document.getElementById(id).style.display === 'none') {
// Do something
// disconnect perhaps.
}
}
}
else if (mutation.type === 'attributes') {
// If the style is inline this may work too.
console.log('The ' + mutation.attributeName + ' attribute was modified.');
console.log(mutation.attributeName === 'style');
let id = mutation.target.id;
if(document.getElementById(id).style.display === 'none') {
// Do something
// disconnect perhaps.
}
}
}
};
// Create an observer instance linked to the callback function
const observer = new MutationObserver(callback);
// Start observing the target node for configured mutations
observer.observe(targetNode, config);
// Later, you can stop observing
observer.disconnect();
https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver

Chrome Extension: Waiting For Element to Load (async js)

I have a Chrome extension, and I want to wait until an element is loaded before injecting content into the page.
I'm trying to inject a button:
myButton = document.createElement('button');
myButton.class = 'mybutton';
document.querySelector('.element_id').appendChild(myButton)
I have this at the top of my content script. It used to work just fine, but then it stopped working. The error that was displayed was:
Uncaught TypeError: Cannot read property 'appendChild' of null
In order to wait for the element with class id .element_id to load, I tried to use a MutationObserver
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if (!mutation.addedNodes) return
for (var i = 0; i < mutation.addedNodes.length; i++) {
if (mutation.addedNodes[i].parentNode == document.querySelector('#outer-container')) {
myButton = document.createElement('button');
myButton.class = 'mybutton';
document.querySelector('.element_id').appendChild(myButton)
}
var node = mutation.addedNodes[i]
}
})
})
observer.observe(document.body, {
childList: true
, subtree: true
, attributes: false
, characterData: false
})
When I used the mutation observer, the page would load an outer div element called outer-container, and there was no way for me to directly compare the class .element_id. The class .element_id is nested a number of layers into the outer div.
HOWEVER, the above did not work, and I still received the null property error.
Is there a better way to wait for some element to be loaded (which is loaded async), before injecting?
Don't forget to add childList and subtree property when observing changes.
var observer = new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
if (!mutation.addedNodes) {
return;
}
for (var i = 0; i < mutation.addedNodes.length; i++) {
if (mutation.addedNodes[i].classList.contains("element_id")) {
// Your logic here
}
}
});
});
observer.observe(document.body, {
childList: true,
subtree: true
});
An insertion into DOM may have the element in question deeper in the added node.
For example, this can be inserted into the DOM:
<div class="container">
<div class="element_id">...</div>
...
</div>
In that case, the added node list will only contain the .container node.
The mutation will not list everything added, it's your responsibility to recursively dig into the added fragment looking through added nodes.
Using mutation-summary library may help you avoid such headaches.
var observer = new MutationSummary({
rootNode: document.body,
callback: function(summaries) {
summaries.forEach(function(summary) {
summary.added.forEach(function(idElement) {
/* ... */
idElement.appendChild(myButton);
});
});
},
queries: [{element: ".element_id"}]
});
If you don't want to use a library, you can try calling querySelector or querySelectorAll on addedNodes[i].

Why does MutationObserver fire twice for childList but never for characterData?

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>

How to use MutationObserver?

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

Categories

Resources