MutationObserver characterData usage without childList - javascript

Up until recently I thought that childList : true on MutationObserver was to be used when child node is being added/removed
e.g from <span id='mama-span'> </span> to <span id='mama-span'><span id='baby-span'></span></span>
and characterData : true was to be used when text inside observed element chages e.t <span> </span> to <span> with some text </span>. It turns out that for text change to be observed one needs to add childList : true.
Can anyone think of situation in which characterData would be used without childList? What is it used for?

You can observe a text node directly. In that case you don't need to observe childList. There are many cases where it could be useful, in a contenteditable element for example. Like this:
// select the target node
var target = document.querySelector('#some-id').childNodes[0];
// 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: false, characterData: true };
// pass in the target node, as well as the observer options
observer.observe(target, config);
<div id='some-id' contenteditable='true'>Modify content</div>

Related

Detect css changes (mutationObserver on styleSheet css)

Is there a way to detect stylesheet changes ? MutationObserver only tracks inline css changes on the element.
html
<div class="exampleClass"></div>
js
let config = {
attributes: true,
// attributeFilter: ["style"],
};
let mutationCallback = function(mutationsList) {
mutationsList.forEach((mutation, i) => {
console.log(mutation);
});
};
let observer = new MutationObserver(mutationCallback);
observer.observe(document.querySelector('.exampleClass'), config);
If I modify the element throught js with
document.querySelector(‘exampleClass’).style.top = '10px'
or the webconsole inspector directly on the node, the mutation observer callback is called, but if the class (not the node itself) is modified in the webconsole inspector there is no callback

Mutation Observer is triggered even when attribute value do not change

When I change an attribute value with the same value, looking at the inspector console, the DOM tree does not change, yet Mutation Observer triggers since I modified the attribute value, but for the actual same value.
Can someone explains how this works under the hood? I inserted a snippet to demonstrate my point.
/* OBSERVER */
var divToUpdate = document.querySelector('#update');
var config = {attributeFilter: ['data-update']};
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if (mutation.target.dataset.update == 'true') {
console.log('Attribute value updated, but not really!', mutation);
}
});
});
observer.observe(divToUpdate, config);
/* BUTTON UPDATER */
document.querySelector('button').addEventListener('click', function() {
divToUpdate.setAttribute('data-update', true);
});
<div id="update" data-update="true">DIV WITH ATTRIBUTE</div>
<button type="button">UPDATE DIV ATTRIBUTE</button>
According to this history, MutationObserver was designed to work that way. Any call to setAttribute triggers a mutation, regardless of whether the value is being changed or set to the current value. https://github.com/whatwg/dom/issues/520#issuecomment-336574796

Fire an event when a liste get new item

i have an empty list :
<ul id="select2-choices"></ul>
this list gets elements added to it using a ui ,so i get this :
<ul id="select2-choices">
<li>item1</li>
<li>item2</li>
</ul>
i want to fire an event , in order to call a function when that list get a new item :
$("#select2-choices").on("Thevent", function (e)){
self.SetDefaultTeam(e);
});
how to do that ?
You can use mutation observers as shown below. The code is commented. I created a button to mimic the addition of new items, but the mutation observer is the function that recognises that change in the DOM tree.
N.B. If you have access to the code that is adding the new li then it would be better to trigger your function from there.
Let me know if you were hoping for something else.
// Create a button to add options to mimic your functionality
$("#add-li").click(function() {
$("ul#select2-choices").append("<li>New</li>");
});
// Create mutation observer
var observer = new MutationObserver(function(mutations) {
// Something has changed in the #select2-choices
console.log("Change noticed in childList");
});
// Just look out for childList changes
var config = {
attributes: false,
childList: true,
characterData: false
};
// Select target
var target = document.querySelector('#select2-choices');
// Launch observer with above configuration
observer.observe(target, config);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<ul id="select2-choices">
</ul>
<button id="add-li">Add option</button>

MutationObserver not showing all mutations?

I've got a 3rd-party script that loads a photo gallery on my page with the images coming from off-site.
My page starts as empty:
<div class="container-fluid" id="cincopa">
</div>
The 3rd-party script then adds other stuff (like the frame of the photo gallery):
<div class="container-fluid" id="cincopa">
<div id="cp_widget_38cc1320-f4a4-407a-a80e-1747bd339b64">
</div>
</div>
Then finally the images load:
<div class="container-fluid" id="cincopa">
<div id="cp_widget_38cc1320-f4a4-407a-a80e-1747bd339b64">
<div class="galleria_images">
<div class="galleria_image">SomeImage</div>
<div class="galleria_image">SomeImage</div>
<div class="galleria_image">SomeImage</div>
</div>
</div>
</div>
I want to:
display a loading animation
set a MutationObserver on $('#cincopa')
when it detects that $('.galleria_image') has been created, it means images have been loaded, so I can
remove the loading animation
Code:
var target = document.querySelector('#cincopa');
// create an observer instance
var observer = new MutationObserver(function(mutations) {
console.log(mutations);
mutations.forEach(function(mutation) {
console.log(mutation.type);
});
});
// configuration of the observer:
var config = { attributes: true, childList: true, characterData: true };
// start the observer, pass in the target node, as well as the observer options
observer.observe(target, config);
The problem is that the MutationObserver only console logs one mutation and the MutationRecord only has one mutation in its array. I would expect numerous mutations as the 3rd-party script creates DOM elements.
Am I misunderstanding how MutationObserver works?
Here's the solution
// This is MeteorJS creating the loading spinning thing
var loadingView = Blaze.render(Template.loading, $('#cincopa')[0]);
// select the target node
var target = document.querySelector('#cincopa');
// create an observer instance
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if(mutation.target.className === "galleria_image"){
// a image has been loaded, so remove the loading spinner and
// kill the observer
Blaze.remove(loadingView);
observer.disconnect();
}
});
});
// configuration of the observer:
var config = { attributes: true, childList: true, characterData: true, subtree: true };
// start the observer, pass in the target node, as well as the observer options
observer.observe(target, config);
Updated Solution
.forEach is dumb and doesn't have a good way to break out of the loop, which meant that I was getting multiple commands to Blaze.remove() and observer.disconnect(), even after .galleria_image had been found.
So I used underscore instead:
// create an observer instance
var observer = new MutationObserver(function(mutations) {
var loaded = _.find(mutations, function(mutation){
console.log("observer running");
return mutation.target.className === "galleria-image";
});
if(loaded){
Blaze.remove(loadingView);
observer.disconnect();
console.log("observer stopped");
};
});
There's an option to allow you to do exactly what you want: observe the subtree of an element. Just add subtree: true to your config for the MutationObserver.
// ...
// In this case case only these two are needed, I believe.
var config = {
childList: true,
subtree: true
};
// ...observe
This should allow you to figure when .gallaria_images has been inserted. As a side note, you (OP) should also double check that images are loaded when that happens.

Can a single MutationObserver object observe multiple targets?

I would like to use a MutationObserver object to observe changes to some of my DOM nodes.
The docs give an example of creating a MutationObserver object and registering it on a target.
// 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);
Say I have the code above, but just under it, I place this code:
var target2 = document.querySelector('#some-other-id');
var config2 = {attributes: true, subtree: true};
observer.observe(target2, config2);
Will observer:
now be observing 2 targets?
will it stop observing target?
will it decide not to observe target2?
will it throw an error?
or will it exhibit some other behavior?
The observer will now be watching two targets - target and target2 per your definitions. No error will be thrown, and target will not be "unregistered" in favor of target2. No unexpected or other behaviors will be exhibited.
Here is a sample which uses the same MutationObserver on two contenteditable elements. To view this, delete the <span> node from each contenteditable element and view the behavior span across both observed elements.
<div id="myTextArea" contenteditable="true">
<span contenteditable="false">Span A</span>
</div>
<div id="myTextArea2" contenteditable="true">
<span contenteditable="false">Span B</span>
</div>
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
//console.log($(mutation.removedNodes)); // <<-- includes text nodes
$(mutation.removedNodes).each(function(value, index) {
if(this.nodeType === 1) {
console.log(this)
}
});
});
});
var config = { attributes: true, childList: true, characterData: true };
observer.observe($('#myTextArea')[0], config);
observer.observe($('#myTextArea2')[0], config);
JSFiddle Link - demo
Note that I have recycled the same config for this first demo, but, placing a new config will be exclusive to that observed element. Taking your example as defined in config2, if used on #myTextArea2, you'll not see the logged node per the configuration options, but notice that the observer for #myTextArea is unaffected.
JSFiddle Link - demo - configuration exclusiveness

Categories