MutationObserver to remove a div when style changes - javascript

I want to remove a div when the style changes. I have read that MutationObserver can do this. But the code that I tried is not working.
const observer = new MutationObserver(function
(mutations) {
mutations.forEach(function (mutation) {
if (mutation.attributes === 'style') {
removeDiv()
}
})
})
const elem = document.querySelector('.show-pl');
observer.observe(elem, {
attributes: true
})
function removeDiv() {
Object.assign(elem.style, {
display: 'none',
})
}

The addition of a new element has nothing to do with attributes, and you can't observe mutations on an element that isn't in the DOM yet.
Instead, you look for childList modifications on the parent element the div will be added within (or childList + subtree on an ancestor, if you can't watch the parent directly —if necessary, the ancestor can even be document.body).
Here's an example watching the parent directly:
// Set up the observer on the container
let observer = new MutationObserver(function() {
// Does the div exist now?
const div = document.querySelector(".show-pl");
if (div) {
// Yes, "remove" it (you're really just hiding it) and release this observer
console.log("The div has appeared, hiding and releasing observer");
div.style.display = "none";
observer.disconnect();
observer = null;
}
});
observer.observe(document.getElementById("the-parent"), {
childList: true
});
console.log("Watching for the element");
// On a delay, create the div
setTimeout(() => {
console.log("Creating the div");
const div = document.createElement("div");
div.textContent = "Hi there!";
div.className = "show-pl";
document.getElementById("the-parent").appendChild(div);
}, 800);
<div id="the-parent"></div>
To watch an ancestor instead you'd use the ancestor in the observe call and add subtree: true to the init options.

function removeDiv(canvas) {
var elem = document.querySelector('.show-pl');
elem.parentNode.removeChild(elem);
}
var observer = new MutationObserver(function (mutations, me) {
var canvas = document.querySelector(".show-pl");
if (canvas) {
removeDiv(canvas);
me.disconnect();
return;
}
});
observer.observe(document, {
childList: true,
subtree: true
});
This does what I want, but an error occurs "cannot read property 'addeventlistener' of null", because the div doesn't exists in the DOM yet.

Related

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

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

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

How to trigger a javascript function on appearence of an element

Hi,
I need to execute a javascript function once as soon as an element with a given class appears on the code (the element will be generated by another script).
This is my function:
play(sound);
the element would appear inside this:
<div id="canvas">
The element would look like this:
<span class="sound">sound name</span>
where "sound name" will determine the argument for play();
how can this be done with javascript?
Thank you.
You can use a You could use a MutationObserver as shown below.
The second argument to .observe(), MutationObserverInit, is important:
In the options, use childList: true if the span will only be added as a direct child. subTree: true if it can be at any level down below #canvas.
From the docs:
childList: Set to true if additions and removals of the target node's child elements (including text nodes) are to be observed.
subtree: Set to true if mutations to target and target's descendants are to be observed.
$("#go").click(function () {
$("#canvas").append($('<span class="sound">sound name</span>'));
});
function play(n) { alert('playing '+ n); }
var obs = new MutationObserver(function(mutations, observer) {
$.each(mutations, function (i, mutation) {
var addedNodes = $(mutation.addedNodes);
var selector = "span.sound"
var spanSounds = addedNodes.find(selector).addBack(selector); // finds either added alone or as tree
spanSounds.each(function () { // handles any number of added spans
play($(this).text());
});
});
});
obs.observe($("#canvas")[0], {childList: true, subtree: true});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="canvas"></div>
<button id="go">Add span to canvas</button>
Using plain JavaScript
The code is a little less compact, but it is definitely doable:
document.getElementById("go").addEventListener('click', function () {
var s = document.createElement('span');
s.innerText = 'sound name';
s.classList.add('sound')
document.getElementById('canvas').appendChild(s);
});
function play(n) { alert('playing '+ n); }
var obs = new MutationObserver(function(mutations, observer) {
for(var i=0; i<mutations.length; ++i) {
for(var j=0; j<mutations[i].addedNodes.length; ++j) {
var addedNode = mutations[i].addedNodes[j];
//NOTE: if the element was added as child of another element, you would have to traverse
// the addedNode to find it. I recommend the jQuery solution above if that's the case
if(addedNode.tagName == "SPAN" && addedNode.classList.contains("sound")) {
play(addedNode.innerText);
}
}
}
});
obs.observe(document.getElementById('canvas'), {childList: true, subtree: true});
<div id="canvas"></div>
<button id="go">Add span to canvas</button>
You could probably try onload event
You need a listener to detect the DOM change, MutationObserver
// Select the node that will be observed for mutations
var targetNode = document.getElementById('some-id');
// Options for the observer (which mutations to observe)
var config = { attributes: true, childList: true };
// Callback function to execute when mutations are observed
var callback = function(mutationsList) {
for(var mutation of mutationsList) {
if (mutation.type == 'childList') {
console.log('A child node has been added or removed.');
}
else if (mutation.type == 'attributes') {
console.log('The ' + mutation.attributeName + ' attribute was modified.');
}
}
};
// Create an observer instance linked to the callback function
var observer = new MutationObserver(callback);
// Start observing the target node for configured mutations
observer.observe(targetNode, config);
// Later, you can stop observing
observer.disconnect();

DOM Mutation Observer Callback and Child class for changed events

I have the following DOM Mutation Observer code:
<script type="text/javascript">
var targetNodes = $("#history");
var MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
var myObserver = new MutationObserver (mutationHandler);
var obsConfig = { childList: true, characterData: true, attributes: true, subtree: true };
//--- Add a target node to the observer. Can only add one node at a time.
targetNodes.each ( function () {
myObserver.observe (this, obsConfig);
} );
function mutationHandler (mutationRecords) {
console.info ("mutationHandler:");
mutationRecords.forEach ( function (mutation) {
$("span.badge").show();
} );
}
</script>
It is working fine when events changes are detected in #history id.
<p id="history"></p>
The problem is that i have some p.class inside #history as follows:
<p id="history">
<p class="mine"></p>
<p class="theirs"></p>
</p>
i need to detect the observer changes only in p class="theirs".
How is that possible only with child class, rather than observing DOM changes in #history id as a whole...
Introduction:
when you use the format:
$("#history").each ( function () {
myObserver.observe (this, obsConfig);
});
this is useless. $("#history") returns one element or nothing.
To test if a value is returned you may use
$("#history").length
This value in your case is 1. Remember that cannot exist more than one element with the same id (refer to: # selector).
In the each loop you use the this keyword. The value of this keyword is "Node target" element required from observe function.
So, because you have only one history element it's completely useless to cycle con only one element (refer: each function). Use the value by itself.
This value can be searched also with:
var target = document.getElementsByClassName('theirs')[0];
or
target = document.querySelectorAll('.theirs')[0];
or
target = $('.theirs').get(0);
Of course, if you do not have such new element on which to observe you cannot use the observe function.
For details see MutationObserver
The best way is to test the return value of the selected element, for instance:
if ($('.theirs').length == 0) {
// raise error and stop
} else {
target = $('.theirs').get(0);
}
Instead, if you have more than one element you may continue to use the each loop:
$(".theirs").each ( function () {
myObserver.observe (this, obsConfig);
});
My proposal:
According to HTML Paragraph tag you cannot have nested paragraphs.
If you need to observe only what happens for your 'theirs' paragraph you need simply to change a bit your code:
$(function () {
var targetNodes = $(".theirs");
var MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
var myObserver = new MutationObserver(mutationHandler);
var obsConfig = {childList: true, characterData: true, attributes: true, subtree: true};
// get the target node on which to observe DOM mutations
var target = document.getElementsByClassName('theirs')[0];
// another way to get the target is
target = document.querySelectorAll('.theirs')[0];
// another way to get the target is
if ($('.theirs').length == 0) {
// raise error and stop
} else {
target = $('.theirs').get(0);
}
myObserver.observe(target, obsConfig);
function mutationHandler(mutationRecords) {
alert("mutationHandler:");
mutationRecords.forEach(function (mutation) {
$("span.badge").show();
});
}
$('#btn').on('click', function (e) {
$('.theirs').text('Date Now is: ' + Date.now());
});
});
<script src="https://code.jquery.com/jquery-1.12.1.min.js"></script>
<div id="history">
<p class="mine"></p>
<p class="theirs"></p>
</div>
<button id="btn">Add text to theirs</button>
If you are interested in changes happening only for paragraphs added or changed inside the div with class theirs, according to the MutationRecord in the following I report only a demo on how to filter the events for new such added nodes (remember to start and stop the observer):
var myObserver = null;
function mutationHandler(mutationRecords, mutationInstance) {
// new node added
if (mutationRecords.length == 1 && mutationRecords[0].addedNodes.length == 1) {
// if element added is a paragraph with class theirs....
var eleAdded = $(mutationRecords[0].addedNodes[0]);
if (eleAdded.is('p.theirs')) {
alert("mutationHandler: added new paragraph with class theirs: " + eleAdded.text());
}
}
// if you need to listen for other events like attribute changed or element removed... please read the documentation regarding mutationRecords object
}
$(function () {
$('#startObserver').on('click', function(e) {
var targetNodes = $('#history');
var target = null;
if (targetNodes.length != 1) {
alert('Cannot start Observer on no element!')
return;
} else {
target = targetNodes.get(0);
}
var MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
myObserver = new MutationObserver(mutationHandler);
var obsConfig = {childList: true, characterData: true, attributes: true, subtree: true};
myObserver.observe(target, obsConfig);
});
$('#stopObserver').on('click', function(e) {
if (myObserver === null) {
alert('Cannot stop an Observer never started!')
} else {
myObserver.disconnect();
myObserver = null;
}
});
$('#btnMine').on('click', function (e) {
var txt = $('#mineInput').val();
$('#history').prepend('<p class="mine">Added mine paragraph with text: ' + (txt.trim() ? txt : 'empty text!') + '</p>');
});
$('#btnTheirs').on('click', function (e) {
var txt = $('#theirsInput').val();
$('#history').append($('<p class="theirs">Added theirs paragraph with text: ' + (txt.trim() ? txt : 'empty text!') + '</p>'));
});
});
<script src="https://code.jquery.com/jquery-1.12.1.min.js"></script>
<div id="history">
</div>
Mine paragraphs text: <input id="mineInput" type="text"><br>
Theirs paragraphs text: <input id="theirsInput" type="text"><br>
<button id="btnMine">Add new Mine paragrapgh</button>
<button id="btnTheirs">Add new Theirs paragrapgh</button><br><br>
<button id="startObserver">Start Observer</button>
<button id="stopObserver">Stop Observer</button>

Categories