I have a div that is getting its inner text changed by another element. In my js file I have the following, which on DOM ready state, starting monitoring the target for mutations. If I put, for example, console.log() of any of the vars inside changeToVar function, it behaves as expected and accesses the variables. What I want though is to be able to access the MON_DATA and CONT_DATA outside of the changeToVar function.
document.addEventListener("DOMContentLoaded", function (event) {
var target = document.getElementById("recidRecord");
var observer = new MutationObserver(function (mutations) {
changeToVar(target.innerText);
});
observer.observe(target, {
attributes: true,
childList: true,
characterData: true,
});
});
function changeToVar(target) {
window.data = target;
var CONST_DATA = CONST_DETAILS[window.data];
window.MON_DATA = CONST_DATA.monitored_props;
window.CONT_DATA = CONST_DATA.contingencies;
}
Related
The MutationObserver is a simple API that allows me to monitor DOM changes. I'm using it in a chrome-extension I want to know when certain elements on a webpage are added. and using the childList option which is supposed to tell me when the specified target adds new nodes.
From Mozilla Docs: https://developer.mozilla.org/en-US/docs/Web/API/MutationObserverInit/childList
By setting childList to true, your callback will be invoked any time nodes are added to or removed from the DOM node or nodes being watched.
but on the callback the change.addedNodes and change.removedNodes is false. even though printing the targets' childNodes.length shows that the number of children is actuall changing
let content_box = document.querySelector('div#content-box'); // target to monitor
// create observer with callback
let observer = new MutationObserver( (mutations, observer) => {
mutations.forEach( change => {
console.log(`Added: ${change.addedNodes==true}`) // no added nodes (there should be)
console.log(`Removed ${change.removedNodes==true}`) // no removed nodes (which is fine)
console.log(change.target) // I've confirmed the target is the correct one
console.log(change.target.childNodes.length) // after multiple callbacks the length of children is actually increasing
})
})
observer.observe(content_box, {
childList: true, // monitoring for childList changes
attributes: false,
subtree: false,
})
You wanna check whether the property addedNodes is in the object of change. so console.log("addedNodes" in change) to get a true result.
let content_box = document.querySelector('div#content-box'); // target to monitor
// create observer with callback
let observer = new MutationObserver( (mutations, observer) => {
mutations.forEach( change => {
console.log(`Added: ${"addedNodes" in change}`) // no added nodes (there should be)
console.log(`Removed ${"removedNodes" in change}`) // no removed nodes (which is fine)
console.log(change.target) // I've confirmed the target is the correct one
console.log(change.target.childNodes.length) // after multiple callbacks the length of children is actually increasing
})
})
observer.observe(content_box, {
childList: true, // monitoring for childList changes
attributes: false,
subtree: false,
})
I am learning to use the Mutation observer class, mostly here: https://javascript.info/mutation-observer
I know how to listen to changes of attributes, and how to get old attribute values using attributeOldValue. But can I get the new attribute value from the same event? If so, how? Thanks
You can simply query current value directly from the changed object.
// is element is an input
const newValue = document.getElementByID('myElement').value;
// or, if it is a any text element
const newValue = document.getElementByID('myElement').innerHTML;
Also, each MutationRecord have a property named target which contains all detailed information about changed object. So depending on MutationRecord.type (which tells us what type of the change have happened) you can get new value from corresponding property inside MutationRecord.target
We can listen for mutations and take content from the target where it has already happened. (the example is not on your topic, but the principle is similar)
document.addEventListener('DOMContentLoaded', function () {
let targetEl = document.querySelector('body');
function checkChange(mutations, observer) {
//****************************
console.log(mutations[0].target.innerHTML);
//****************************
}
let observer = new MutationObserver(checkChange);
observer.observe(targetEl, {
subtree: true,
childList: true,
characterData: true,
characterDataOldValue: true,
})
})
I've seen from this post about Chromium DevTools that there exists the possibility to add DOM breakpoints. Given that I've a full range of elements to monitor I was trying to find a way to programmatically add such breakpoints. I also read this question about DOM breakpoints but it doesn't seem to give me any useful hint.
To achieve a similar result I've used to instrument the setAttribute() function of such DOM elements replacing it with a wrapper that uses the debugger; instruction to trigger the debugger. Anyway this approach fails when dealing with innerHTML or innerText assignments given that there is no way of achieving operator overloading in js.
Can someone suggest me a practical solution?
You may want to use MutationObserver, to observe for any change to a DOM at given root element. Also you can put debugger there and if devTools is open it should break.
const targetNode = document.getElementById('observed-element');
const config = { attributes: true, childList: true, subtree: true };
// Callback function to execute when mutations are observed
const callback = (mutationList, observer) => {
for (const mutation of mutationList) {
console.log(mutation.type);
console.log(mutation.target);
debugger;
}
};
// 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);
// change class
setTimeout(()=>{
targetNode.setAttribute('class', 'some-class')
}, 0);
// change innerText
setTimeout(()=>{
targetNode.innerText = 'some text';
}, 0);
<div id="observed-element">
</div>
You need to open Devtools-Over-Devtools and get references to instances of DOMModel and DOMDebuggerModel
// open Devtools (Ctrl+Shift+I)
// open DevtoolsOverDevtools (Ctrl+Shift+I in Devtools)
// open sdk.js from Ctrl+P pane
// set breakpoint in function setDOMBreakpoint(e, t)
// set HTML breakpoint in Devtools to pause on the created breakpoint
// inside setDOMBreakpoint(e, t)
window.domModel = e.domModel()
window.domDebuggerModel = this
// resume execution, disable breakpoint
_k = [...domModel.idToDOMNode.keys()][0]
_a = await domModel.querySelectorAll(_k, 'div')
_b = _a.map(e => domModel.idToDOMNode.get(e)).filter(Boolean)
_b.map(e => domDebuggerModel.setDOMBreakpoint(e, 'node-removed'))
// 'subtree-modified' | 'attribute-modified' | 'node-removed'
// now all elements are breakpointed
window.DEBUG = true; // toggles programmatic debugging
flag with a global check debug function, like so:
window.CHECK_DEBUG = function() {
if (window.DEBUG) { debugger; }
}
And then insert the following in any function you’re concerned about debugging:
function foo() {
CHECK_DEBUG();
// foo's usual procedure ...
}
To take this a step further (and to take a page out of Firebug's debug() and undebug() wrapper functions) you can decorate the native JavaScript Function object like so:
Function.prototype.debug = function(){
var fn = this;
return function(){
if (window.DEBUG) { debugger; }
return fn.apply(this, arguments);
};
};
Then you can dynamically debug any function by calling:
foo = foo.debug();
I intend to use MutationObserver on observing the appearance and changing of element's value, but to be honest I'm not sure how this should be implemented.
The target of MO would be div.player-bar and what I'm trying to accomplish is to detect when el-badge__content appears in page and when el-badge__content element value is changed (for example instead 1 would change to 2).
Please note that el-badge__content appears at the same time with the creation of div.new-bar and many times div.new-bar would not be present in the page, that's why I need to listen to div.player-bar.
Is this possible? So far I was thinking of something like this:
var target = document.getElementsByClassName('player-bar')[0];
var config = { attributes: true, childList: true, subtree: true };
const observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
mutation.forEach(function(addedNode) {
var e = addedNode.document.getElementsByClassName('el-badge__content')[0];
if (e) {
console.log("Element appearance/changed")
};
});
});
});
observer.observe(target, config);
Thank you in advance.
mutation is a MutationRecord object that contains the array-like addedNodes NodeList collection that you missed in your code, but it's not an array so it doesn't have forEach. You can use ES6 for-of enumeration in modern browsers or a plain for loop or invoke forEach.call.
A much easier solution for this particular case is to use the dynamically updated live collection returned by getElementsByClassName since it's superfast, usually much faster than enumeration of all the mutation records and all their added nodes within.
const target = document.querySelector('.player-bar');
// this is a live collection - when the node is added the [0] element will be defined
const badges = target.getElementsByClassName('el-badge__content');
let prevBadge, prevBadgeText;
const mo = new MutationObserver(() => {
const badge = badges[0];
if (badge && (
// the element was added/replaced entirely
badge !== prevBadge ||
// or just its internal text node
badge.textContent !== prevBadgeText
)) {
prevBadge = badge;
prevBadgeText = badge.textContent;
doSomething();
}
});
mo.observe(target, {subtree: true, childList: true});
function doSomething() {
const badge = badges[0];
console.log(badge, badge.textContent);
}
As you can see the second observer is added on the badge element itself. When the badge element is removed, the observer will be automatically removed by the garbage collector.
Background
I want to step through a paginated Ajax table and pull out some image src attributes that exist prior to fallback images being swapped in, and store them in an array. While I can easily monitor changes to the attributes with a global MutationObserver, I'd like to only start storing these in the target array when the user clicks a specific button. On click, I would page through the table, store the mutated src attributes in the array, and then stop storing once the process is complete, so the user is then free to manually page through the table without modifying the array further.
Question
Is it possible to initialise a MutationObserver inside a function? If so, what am I doing wrong with the toy example below, which doesn't return the array of mutation objects that I expected? It seems that cb is never called.
function test() {
var a = [];
colors = ['yellow', 'blue', 'red'];
var cb = function(mutations, observer) {
for (var mutation of mutations) {
console.log('In mutations loop:', a);
console.log(mutation);
// Here I would pull out the img src attribute
a.push(mutation);
}
};
var observer = new MutationObserver(cb);
observer.observe(document, {
attributes: true,
attributeOldValue: true,
childList: true,
subtree: true
});
for (var i=0; i < colors.length; i++) {
// For my motivating problem this loop would click through
// the table, triggering img src mutations.
console.log(colors[i]);
document.body.style.cssText = 'background:' + colors[i] + ';';
}
observer.disconnect();
console.log('a:', a);
return(a);
}
test()