Attribute changes with MutationObserver only found if code is inside a loop - javascript

I am having some trouble detecting attribute changes of an html element with js and MutationObserver. This is the code:
document.addEventListener("DOMContentLoaded", function() {
const checkForLoading = setInterval(function () {
let loading = document.getElementById("sequence");
if (loading) {
console.log("loading detected");
const loadingObserver = new MutationObserver(function (mutations) {
console.log("mutation detected");
if (loading.getAttribute('data-dash-is-loading') === 'true') {
console.log("loading");
// loading.style.visibility = 'hidden';
} else {
console.log("not loading");
// loading.style.visibility = 'visible';
}
});
const observerOptions = {
attributes: true,
}
loadingObserver.observe(loading, observerOptions);
clearInterval(checkForLoading);
}
}, 100);
});
Because the element is not available immediately I have the checkForLoading loop set up. The attribute 'data-dash-is-loading' is only set when an element is loading and otherwise not available. This code only works if the loop keeps on running after the sequence element is detected and clearInterval(checkForLoading) is not called. However I would like to avoid running this loop constantly. Any help to fix this issue is greatly appreciated.

It usually means the element is recreated, so you need to observe its ancestor higher up the DOM tree and add subtree: true.
For example, a parent element:
loadingObserver.observe(loading.parentElement, {attributes: true, subtree: true});
If this doesn't help at once you can try document.body first to make sure the mutation actually happens, then try various ancestor elements in-between to find the one that stays the same.
In the callback you'll need to verify that the mutation occurred on your desired element:
for (const m of mutations) {
if (m.target.id === 'foo') {
// it's the desired element, do something about it here and stop the loop
break;
}
}

Related

.js alert(); when text in span change

I got this Code:
const odleglosc = parseFloat(document.getElementsByClassName("dystans")[0].innerText);
const daleko = "za daleko";
if (odleglosc > 200) {
alert(daleko);
}
<span data-pafe-form-builder-live-preview="lechnarlok" class="dystans" id="dystans">500</span>
It runs fine, because at starting point there is number higher than 200 in it.
But when i change it, alert don't trigger again..
How can i solve that? :(
Im not sure about how the span value will change, so this example works with an input. The same idea could also be applied to a span tho.
<input onchange="theFunction()" data-pafe-form-builder-live-preview="lechnarlok" class="dystans" id="dystans" value="500"></input>
<script>
function theFunction() {
var odleglosc = parseFloat(document.getElementsByClassName("dystans")[0].value);
var daleko = "za daleko";
if (odleglosc > 200)
{
alert(daleko);
}
}
</script>
Here, onChange calls the function whenever the value in the input field changes.
Do You want to show alert after each change of the value? If yes, use event listener for input (not for span).
Update:
Use MutationObserver for this case.
let span = document.getElementById('dystans');
function updateValue(value) {
var daleko = "za daleko";
if (parseFloat(value) > 200)
{
alert(daleko);
}
}
// create a new instance of 'MutationObserver' named 'observer',
// passing it a callback function
observer = new MutationObserver(function(mutationsList, observer) {
let value = mutationsList.filter(x => x.target.id =='dystans')[0].target.innerHTML;
updateValue(value);
});
// call 'observe' on that MutationObserver instance,
// passing it the element to observe, and the options object
observer.observe(span, {characterData: true, childList: true, attributes: true});
span.innerHTML = '3000';
<span data-pafe-form-builder-live-preview="lechnarlok" class="dystans" id="dystans">500</span>
Source:
Detect changes in the DOM
https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver

Adding or removing a class to an element dynamically using Mutation Observer

I want to remove a class from an element when a modal pops-up But when I searched online I found DOMNodeInserted and it was working until it went live and the error I got was DOMNodeInserted has been deprecated. The error I keep getting below
enter image description here
CODE WORKING BELOW, but has been deprecated.
$(document).on('DOMNodeInserted', function(e) {
if ( $("body").hasClass('modal-open') ) {
$(".hide-search").hide();
// $(".nav-menu").addClass("border-0");
} else if ($("body").hasClass('modal-open') === false){
$(".hide-search").show();
// $(".nav-menu").removeClass("border-0");
}
});
New code i wanted to Implement but i don't know how to go about it.
let body = document.querySelector('body');
let observer = new MutationObserver(mutationRecords => {
console.log(mutationRecords); // console.log(the changes)
// observe everything except attributes
observer.observe(body, {
childList: true, // observe direct children
subtree: true, // and lower descendants too
characterDataOldValue: true // pass old data to callback
});
});
}
}
observe() should be outside the callback
all you need to observe is the class attribute, nothing else, so there's no need for the extremely expensive subtree:true.
the class may include something else so you need to ignore irrelevant changes
new MutationObserver((mutations, observer) => {
const oldState = mutations[0].oldValue.split(/\s+/).includes('modal-open');
const newState = document.body.classList.contains('modal-open');
if (oldState === newState) return;
if (newState) {
$('.hide-search').hide();
} else {
$('.hide-search').show();
}
}).observe(document.body, {
attributes: true,
attributeFilter: ['class'],
attributeOldValue: true,
});
I was able to resolve the above problem with this solution
function myFunction(x) {
if (x.matches) {
var body = $("body");
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if (mutation.attributeName === "class") {
var attributeValue = $(mutation.target).prop(mutation.attributeName);
console.log("Class attribute changed to:", attributeValue);
if(attributeValue == "ng-scope modal-open") {
$(".input-group").addClass("removeDisplay");
$(".nav-menu").addClass("hide-nav-menu");
} else {
$(".input-group").removeClass("removeDisplay");
$(".nav-menu").removeClass("hide-nav-menu");
}
}
});
});
observer.observe(body[0], {
attributes: true
});
}
}
// Wow It's working.
var x = window.matchMedia("(max-width: 1240px)")
myFunction(x)
x.addListener(myFunction)
Firstly I used a match media to check if the screen is lesser than 1240px size then I used the mutation along with checking if an attribute class is present, then perform some certain actions based on that.

Cannot get an img element that is dynamically added to a page

Let's say I am trying to run code bellow on 9gag to get images that are dynamically added form infinite scroll. I am trying to figure out how to get img element.
//want to do something useful in this function
checkIfImg = function(toCheck){
if (toCheck.is('img')) {
console.log("finaly");
}
else {
backImg = toCheck.css('background-image');
if (backImg != 'none'){
console.log("background fynaly");
}
}
}
//that works just fine, since it is not for dynamic content
//$('*').each(function(){
// checkIfImg($(this));
//})
//this is sums up all my attempts
var observer = new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
switch (mutation.type) {
case 'childList':
Array.prototype.forEach.call(mutation.target.children, function (child) {
if ( child.tagName === "IMG" ) {
console.log("img");
}
child.addEventListener( 'load', checkIfImg, false );
console.log("forEachChild");
console.log(child);
checkIfImg($(child));
$(child).each(function(){
console.log("inside each");
console.log($(this));
if ($(this).tagName == "IMG"){
console.log("img");
}
checkIfImg($(this));
})
});
break;
default:
}
});
});
observer.observe(document, {childList: true, subtree: true});
Observer gets lots of different elements but I can't seem to find any img among them.
You need to check for img elements deeper down the tree, not only the direct children of mutation.children (each of them may contain additional children).
You could do this with a $.find('img'), and use an array to eliminate duplicates:
let imageList = [];
//want to do something useful in this function
function checkIfImg(toCheck) {
// find all images in changed node
let images = toCheck.find('img');
for(let image of images) {
let imageSource = $(image).attr('src');
if(!imageList.includes(imageSource)) {
imageList.push(imageSource);
console.log("image:", imageSource);
}
}
};
// get existing images
checkIfImg($(document));
// observe changes
var observer = new MutationObserver(function(mutations) {
mutations.forEach(mutation => checkIfImg($(mutation.target)));
});
observer.observe(document, { childList: true, subtree: true });

MutationObserver for when parent changes

Is there a way to detect when the parent of an element changes (namely when changing from null to !null -- i.e., when the element is initially added to the DOM) using a MutationObserver? I can't find any documentation that shows how this could be achieved.
I am programmatically creating elements with document.createElement(). I return the created element from a function, but want to create a listener from within the function to react when the element is eventually added to the DOM, without knowing where or which parent it will be added to.
I'm not quite sure how else to phrase this, honestly.
const elem = document.createElement('div');
let added = false;
elem.addEventListener('added-to-dom', () => { added = true; });
// ^ how do I achieve this?
assert(added == false);
document.body.addChild(elem);
assert(added == true);
I don't see what's so hard about understanding this or why it was closed.
An easy but inelegant way is to monkeypatch Node.prototype.appendChild (and, if necessary, Element.prototype.append, Element.prototype.insertAdjacentElement, and Node.prototype.insertBefore) to watch for when an element is added to the DOM:
const elementsToWatch = new Set();
const { appendChild } = Node.prototype;
Node.prototype.appendChild = function(childToAppend) {
if (elementsToWatch.has(childToAppend)) {
console.log('Watched child appended!');
elementsToWatch.delete(childToAppend);
}
return appendChild.call(this, childToAppend);
};
button.addEventListener('click', () => {
console.log('Element created...');
const div = document.createElement('div');
elementsToWatch.add(div);
setTimeout(() => {
console.log('About to append element...');
container.appendChild(div);
}, 1000);
});
<button id="button">Append something after 1000ms</button>
<div id="container"></div>
Mutating built-in prototypes generally isn't a good idea, though.
Another option would be to use a MutationObserver for the whole document, but this may well result in lots of activated callbacks for a large page with frequent mutations, which may not be desirable:
const elementsToWatch = [];
new MutationObserver(() => {
// instead of the below, another option is to iterate over elements
// observed by the MutationObserver
// which could be more efficient, depending on how often
// other elements are added to the page
const root = document.documentElement; // returns the <html> element
const indexOfElementThatWasJustAdded = elementsToWatch.findIndex(
elm => root.contains(elm)
);
// instead of the above, could also use `elm.isConnected()` on newer browsers
// if an appended node, if it has a parent,
// will always be in the DOM,
// instead of `root.contains(elm)`, can use `elm.parentElement`
if (indexOfElementThatWasJustAdded === -1) {
return;
}
elementsToWatch.splice(indexOfElementThatWasJustAdded, 1);
console.log('Observed an appended element!');
}).observe(document.body, { childList: true, subtree: true });
button.addEventListener('click', () => {
console.log('Element created...');
const div = document.createElement('div');
div.textContent = 'foo';
elementsToWatch.push(div);
setTimeout(() => {
console.log('About to append element...');
container.appendChild(div);
}, 1000);
});
<button id="button">Append something after 1000ms</button>
<div id="container"></div>
You could listen for the DOMNodeInserted-event and compare the elements id.
Notice: This event is marked as Depricated and will probably stop working in modern modern browsers at some point in the near
future.
let container = document.getElementById('container');
let button = document.getElementById('button');
document.body.addEventListener('DOMNodeInserted', function(event) {
if (event.originalTarget.id == button.id) {
console.log('Parent changed to: ' + event.originalTarget.parentElement.id);
}
});
button.addEventListener('click', function(event) {
container.appendChild(button);
});
#container {
width: 140px;
height: 24px;
margin: 10px;
border: 2px dashed #c0a;
}
<div id="container"></div>
<button id="button">append to container</button>

Execute some code if element exists

I want to execute some code if this element exists <div id="element">Some text</div> using jquery.
my code until now:
setInterval(function(){check()}, 100);
function check() {
var count = document.getElementById("count");
if(document.getElementById("element") && count.value != 1) {
//some code
count.value = 1;
}
}
It works, but I think this is a very bad way to reach my target.
I want an easier solution, but I didn't find.
Your way is the most reliable, because it cannot fail.
However, you may wish to try listening for change events on the count element, and reset the value if your element exists. This will mean your verification code only runs when a change is made to the value.
do you want to do this initially?
hjow about you do this?
$(document).ready(function(){
if($(#element)) { do something };
});
EDIT:
after 10 seconds of search:
$("#someDiv").bind("DOMSubtreeModified", function() {
alert("tree changed");
});
You can listen DOM events (when an element is inserted or modified) and check your condition only at this time, not every time interval.
If you need some information about DOM events you can take a look at : http://en.wikipedia.org/wiki/DOM_events#HTML_events (mutation events)
One solution I can think of is about using MutationObserver with a fallback mechanism like
jQuery(function() {
if (window.MutationObserver) {
var target = document.querySelector('#myparent');
// create an observer instance
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
[].forEach.call(mutation.addedNodes, function(el, i) {
if (el.id == 'element') {
check();
//if you don't want to listen any more remove the listener
//observer.disconnect();
}
})
});
});
// configuration of the observer:
var config = {
childList: true
};
// pass in the target node, as well as the observer options
observer.observe(target, config);
} else {
setInterval(function() {
if (document.getElementById("element")) {
check();
}
}, 100);
}
function check() {
var count = document.getElementById("count");
if (count.value != 1) {
//some code
count.value = 1;
}
}
});
$('button').click(function() {
$('#myparent').append('<div id="element">Some text</div>');
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<button>Add</button>
<input id="count" />
<div id="myparent"></div>
Note: The solution assumes you have a static parent element for the dynamic element(like the myparent element in the above example)

Categories