I have an observer, which listens any changes of attributes of listitem
divs, and if attibutes is changed, it will set background color of that div to red.
Actually you can easily test it yourself. Open Chrome's bookmark manager, launch this script in console, and then click any bookmark or folder. It will be colored red.
The problem is mentioned in the middle of the code block: if I use setAttribute
, the script will be hanged.
The first method (using style.attributeName
) is not appropriate for me, because in my actual code I use custom attribute, not background-color
.
The problem looks very strange. What's the way it may be solved?
<div role="listitem"></div>
<div role="listitem"></div>
<div role="listitem"></div>
<script>
var target = document.querySelectorAll("[role='listitem']");
for (var i = 0; i < target.length; i++) {
// create an observer instance
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
// The Problem
// ===========
// This work well
mutation.target.style.backgroundColor = "red";
// But this doesn't
// mutation.target.setAttribute("style", "background-color: red;");
});
});
// configuration of the observer
var config = { attributes: true };
// pass in the target node, as well as the observer options
observer.observe(target[i], config);
}
</script>
You're responding to every attribute change by changing an attribute. It's no surprise that that causes a problem: You'll make the observer fire again. Apparently setAttribute
updates the attribute object even though you're setting the same value (which I suppose isn't unreasonable, though it's a bit surprising).
Instead, I would only set it if it actually needs to change:
if (mutation.target.getAttribute("yourattr") != targetvalue) {
mutation.target.setAttribute("yourattr", targetvalue);
}
I've checked that setting the attribute to the same value does fire the observer again (on Chrome, anyway):
var target = document.getElementById("target");
var counter = 0;
var observer = new MutationObserver(function() {
++counter;
console.log(Date.now() + ": " + counter);
if (counter < 10) {
console.log("Inside handler: Setting data-foo to bar");
target.setAttribute("data-foo", "bar");
} else {
console.log("Inside handler: Stopped, to avoid looping forever");
}
});
observer.observe(target, {attributes: true});
console.log("Initial change: Setting data-something-else to testing");
target.setAttribute("data-something-else", "testing");
<div id="target"></div>
So checking first prevents that happening:
var target = document.getElementById("target");
var observer = new MutationObserver(function() {
console.log(Date.now() + ": In handler");
if (target.getAttribute("data-foo") != "bar") {
console.log("Inside handler: Setting data-foo to bar");
target.setAttribute("data-foo", "bar");
} else {
console.log("Inside handler: No need to set it, it's already set");
}
});
observer.observe(target, {attributes: true});
console.log("Initial change: Setting data-something-else to testing");
target.setAttribute("data-something-else", "testing");
<div id="target"></div>