Best way to check when an element is not undefined - javascript

While I'm observing a web-page there is a button that after I click an element appears.
I already have the id of that element, what I want to do in a single code:
press the button, wait for the specific element to appear (become defined), perform an action.
What I tried to do is this:
btn = document.getElementById("btn");
btn.click();
while(document.getElementById("id") == undefined){
continue;
}
console.log("element is loaded!!");
That code didn't work for me (the browser got stuck).
I thought also to pause the code for specific time that it gets to the element to appear (sleep), but is there a better way?
Again, I don't have access to the code of the web-page, so I can't rais a flag when this element is loaded.

Try using a Promise:
btn = document.getElementById("btn");
btn.click();
new Promise((resolve, reject) => {
while (document.getElementById("id") == undefined) {}
resolve();
}).then(() => {
console.log("element is loaded!!");
});

globally, if you want to check if the variable is set :
if(variable)
{
// Do stuff
}

You could set an interval
btn = document.getElementById("btn");
btn.click();
let intv = setInterval(() => {
if (!!document.getElementById("id")) {
console.log('the element is here');
clearInterval(this);
}}, 200);
}
console.log("element is loaded!!");

Use a MutationObserver to check whether the element exists everytime a change in the DOM occurs:
let observer = new MutationObserver(() => document.getElementById('id') ? console.log("loaded") : '');
observer.observe(document.body, {
childList: true
});
Demo:
let observer = new MutationObserver(() => document.getElementById('id') ? console.log("loaded") : '');
observer.observe(document.body, {
childList: true
});
/* below is simply for demonstration */
document.getElementById("btn").addEventListener('click', () => {
setTimeout(() => {
document.body.appendChild(Object.assign(document.createElement("p"), {id: 'id',innerHTML: 'Hello World!'}));
}, 1000)
})
<button id="btn">Click to add an element in 1 second</button>

Your browser is probably crashing because the code is executed too many times in the while loop.
You could try using MutationObserver to listen for change in DOM.
Or
Add use setInterval instead of the while loop.
let interval;
function handleClick() {
// add the new element after 5 seconds
const divNode = document.createElement('div');
divNode.id = 'newElement';
window.setTimeout(() => {
document.body.append(divNode);
}, 5000);
// every second check for the element
interval = window.setInterval(() => {
checkIfElementIsInDom();
}, 1000);
}
function checkIfElementIsInDom() {
console.log('Checking...');
const newNode = document.getElementById('newElement');
if (newNode) {
console.log('completed!');
// stop the interval
clearInterval(interval);
}
}
const buttonNode = document.getElementById('button');
buttonNode.addEventListener('click', handleClick.bind(this));
<button type="button" id="button">Click</button>

Here is an example of using a MutationObserver
const targetNode = document.querySelector('.test');
// Options for the observer (which mutations to observe)
const config = { attributes: true, childList: true, subtree: true };
// Callback function to execute when mutations are observed
const callback = function(mutationsList, observer) {
// Use traditional 'for loops' for IE 11
for(const mutation of mutationsList) {
if (mutation.type === 'childList') {
console.log('A child node has been added or removed.');
// Check for our new item
const itm = document.querySelector('.itm');
if (itm) {
console.log('we found our item');
observer.disconnect();
}
}
}
};
// Create an observer instance linked to the callback function
const observer = new MutationObserver(callback);
document.querySelector('button').addEventListener('click', () => {
// Start observing the target node for configured mutations
observer.observe(targetNode, config);
setTimeout(() => {
// Add an item.
document.querySelector('.test').innerHTML = '<div class="itm">Here it is</div>';
}, 5000);
});
<section>
<div>
<button>Click Me</button>
</div>
</section>
<section class="test">
</section>

Related

Intersection Observer trigger when element is visible (before scrolling)

I am trying to work with the Intersection Observer API. I have a function which works in my first iteration. The basic logic is that if the user scrolls down and adds or removes items from a basket, once the basket is in view again (as it is at the top of the document) then I fire an API call.
The issue is that it will not fire the function before scrolling, I want to trigger it if the item is visible or becomes visible again after scrolling (the second part is working)
Here is original js:
var observerTargets = document.querySelectorAll('[id^="mini-trolley"]');
var observerOptions = {
root: null, // null means root is viewport
rootMargin: '0px',
threshold: 0.01 // trigger callback when 1% of the element is visible
}
var activeClass = 'active';
var trigger = $('button');
var isCartItemClicked = false;
trigger.on('click', function() {
isCartItemClicked = true;
});
function observerCallback(entries, observer) {
entries.forEach(entry => {
if(entry.isIntersecting && isCartItemClicked){
$(observerTargets).removeClass(activeClass);
$(entry.target).addClass(activeClass);
isCartItemClicked = false;
console.log('isCartItemClicked and in view');
// do my api call function here
} else {
$(entry.target).removeClass(activeClass);
}
});
}
var observer = new IntersectionObserver(observerCallback, observerOptions);
[...observerTargets].forEach(target => observer.observe(target));
I have updated this so it now checks if the item is visible. so I have updated:
if(entry.isIntersecting && isCartItemClicked)
to
if((entry.isVisible || entry.isIntersecting) && isCartItemClicked)
The issue as I understand is that the observer is only triggered on scroll, but the entry.isVisible is part of the observer callback function.
I have made a JSFIDDLE here (which has HTML and CSS markup).
Is it possible to modify the code. Weirdly the MDN page does not mention the isVisible property, but it is clearly part of the function.
This one is a little tricky but can be done by creating a someObserverEntriesVisible parameter that is set by the observerCallback. With that in place we can define how the button triggers should be handled separately from the observer callback for each intersecting entry.
const observerTargets = document.querySelectorAll('[id^="mini-trolley"]');
const observerOptions = {
root: null, // null means root is viewport
rootMargin: '0px',
threshold: 0.01 // trigger callback when 1% of the element is visible
};
const activeClass = 'active';
const trigger = $('button');
let isCartItemClicked = false;
let someObserverEntriesVisible = null;
let observerEntries = [];
trigger.on('click', () => {
isCartItemClicked = true;
if (someObserverEntriesVisible) {
console.log('fired from button');
observerCallback(observerEntries, observer, false);
}
});
function observerCallback(entries, observer, resetCartItemClicked = true) {
observerEntries = entries;
someObserverEntriesVisible = false;
entries.forEach(entry => {
someObserverEntriesVisible ||= entry.isIntersecting;
if (entry.isIntersecting && isCartItemClicked) {
$(entry.target).addClass(activeClass);
// add API call here
if (resetCartItemClicked) {
isCartItemClicked = false;
console.log('fired from observer');
}
} else {
$(entry.target).removeClass(activeClass);
}
});
}
const observer = new IntersectionObserver(observerCallback, observerOptions);
[...observerTargets].forEach(target => observer.observe(target));
#content {
height: 500px;
}
.active {
background-color: orange;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="mini-trolley">Observer target1</div>
<button>Top button</button>
<div id="content"></div>
<div id="mini-trolley">Observer target2</div>
<button>Bottom button</button>

Execute JavaScript on div change

I have two scripts. One asks my backend about new data in database and if there is something new, add this to div with id 'box'. In second script I want to monitorize this and if something is changed in this div, I want to call my function. How to do that?
$(document).ready(function(){
refreshTable();
});
function refreshTable(){ //loading data to div
$('#messbox').load('getData.php', function(){ setTimeout(refreshTable, 1000); });
}
function scroll(){ //function to call
$("#messbox").scrollTop($("#messbox")[0].scrollHeight);
}
You can use a MutationObserver to listen for changes in the element's childList:
const targetNode = document.getElementById('target');
const callback = function(mutationsList, observer) {
for(const mutation of mutationsList) {
if (mutation.type === 'childList') {
myFunction()
}
}
};
const observer = new MutationObserver(callback);
observer.observe(targetNode, {childList: true});
function myFunction(){
console.log("Change!")
}
<div id="target"></div>
<button onclick="target.innerHTML += '<p>Hello World!</p>'">Add something to #target</button>

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.

How to keep track of DIV's child element count in runtime?

I have a div, which will contain dropdowns and these dropdowns are created dynamically by the user on the click oo a button which is kept outside this div.
So what I need to achieve here is I wanna display 'No filter applied' when there are no dropdowns and remove that 'No filter applied' while there are dropdowns present.
I tried this scenario through addEventListener but I am not sure what action needs to implement for this scenario?
document.addEventListener("DOMContentLoaded", function(event) {
var activities = document.getElementById("dvContainer");
activities.addEventListener("change", function() {
if (activities.childElementCount > 0) {
activities.classList.add("displayZero");
} else {
activities.classList.remove("displayZero");
}
//console.log('ajay');
});
});
function AddDropDownList() {}
<input type="button" id="btnAdd" onclick="AddDropDownList()" value="Add Filter" />
<div id="dvContainer"><span>No Filters applied.</span></div>
This is my try, thanks in advance.
According to what you mentioned, you have a button that with click it, you add dropdowns dynamically.
so you don't need any extra event!!
in your button's click function:
yourButton.onclick=function(){
//..... do somethings similar adding dropdowns
activities.classList.add("displayZero");
};
And where you remove dropdown:
activities.classList.remove("displayZero");
Currently, I can think of only 2 ways to resolve:
The Easiest solution is first to create update function then call it from init of dom and then in the AddDropDownList. E.g.
function update() {
if (activities.childElementCount > 0) {
activities.classList.add("displayZero");
} else {
activities.classList.remove("displayZero");
}
}
window.onload = function() {
update();
}
function AddDropDownList() {
//Put Your code as you have written and then add
update();
}
Use Mutation Observer
window.onload = function() {
// Select the node that will be observed for mutations
var targetNode = document.getElementById('dvContainer');
// Options for the observer (which mutations to observe)
var config = {
attributes: true,
childList: true,
subtree: true
};
// 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);
}
function AddDropDownList() {
}
// 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.');
if (activities.childElementCount > 0) {
activities.classList.add("displayZero");
} else {
activities.classList.remove("displayZero");
}
} else if (mutation.type == 'attributes') {
console.log('The ' + mutation.attributeName + ' attribute was modified.');
}
}
};

Wait for specific text in DOM element before continuing

I have a function that should wait for some text to change before it returns a value:
function getElementText() {
while(isLoading()) {
}
return $('#element').text();
}
function isLoading() {
return $('#element')[0] &&
$('#element').text().indexOf('Loading') >= 0;
}
However I think the empty while is not a good option (will it block the event loop?)
No need of jQuery or any other external library, you can simply use MutationObserver: https://developer.mozilla.org/en/docs/Web/API/MutationObserver
Here a simple example, you will have a notice of type characterData when your text changes (after 5 seconds in my example):
// select the target node
var target = document.getElementById('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, attributes: true, subtree: true };
// pass in the target node, as well as the observer options
observer.observe(target, config);
// later, you can stop observing
//observer.disconnect();
setTimeout(function() {
target.innerText = 'Changed text!';
}, 5000);
<div id="some-id">
AAA
</div>
Remove from the config of the observer all the properties you don't need to spy for changes
Elegant way with rxjs:
var source = document.getElementById('source');
var target = document.getElementById('target');
Rx.Observable.fromEvent(source, 'keyup')
.filter( (e) => e.target.value === 'Admin' )
.subscribe( () => target.innerText = "Matched." );
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.4.0/Rx.min.js"></script>
<input id="source" /> <strong>input 'Admin' to trigger</strong>
<p id="target">Not match.</p>
The DOMSubtreeModified event will be triggered when a change occurs in the element, so you can use that to detect if the text is loaded.
You can use a callback function to return the value when it has loaded. Or even better: (jQuery) promises!
var element = document.getElementById('element');
function isLoading() {
return element.innerText.indexOf('Loading') >= 0;
}
function getElementText() {
var def = $.Deferred();
if (!isLoading()) {
def.resolve(element.innerText);
return def.promise();
}
$(element).on('DOMSubtreeModified', function () {
if (!isLoading()) {
def.resolve(element.innerText);
}
});
return def.promise();
}
getElementText().then(function (text) {
// Text is loaded!
alert(text);
});
// Load text after 3 seconds for demonstration
setTimeout(function () {
element.innerText = 'Changed!';
}, 3000);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="element">Loading</div>

Categories