So I am currently using the following code to execute my jQuery. However, as you can see I have set some timers on executing the second part of the code. What I would like is to see if #objectPrice exists, and then run the timed code but only once. This way I can replace the timers.
var checkExist = setInterval(function() {
if (jQuery('#objectPrice').length) {
var content = jQuery('#prisen').html();
objectPrice.value = content.replace(/\D/g,'');
}
}, 200); // check every 200ms
setTimeout(function() { jQuery("#downPayment").focus(); }, 2200);
setTimeout(function() { jQuery("#downPayment").blur(); }, 2205);
The right way to do it
You can do this by observing the direct parent of the element you want to check, let's assume that your element will be added to the page in the following div:
<div id="parent-div">
<div>child 1</div>
<div>child 2</div>
</div>
You will need to observe any new child added to the #parent-div and trigger a function when this happens:
const callback = function(mutationsList, observer) {
for(let mutation of mutationsList) {
if( mutation.addedNodes.length > 0){
for(let node of mutation.addedNodes) {
let jqueryNode = jQuery(node);
if(jqueryNode.is('#objectPrice')){
var content = jQuery('#prisen').html();
objectPrice.value = content.replace(/\D/g,'');
}
}
}
}
};
const observer = new MutationObserver(callback);
observer.observe(jQuery('#parent-div')[0], {childList: true});
But if you don't want to change your current code, you make it recursive and call it the first time, like the following:
var checkingFunction = function () {
if(jQuery('#objectPrice').length){
var content = jQuery('#prisen').html();
objectPrice.value = content.replace(/\D/g,'');
}else{
setTimeout(function() { checkingFunction() }, 200);
}
}
checkingFunction();
Related
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>
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>
Assuming I am unable to see below code, I have no idea how long does the timeout set, and I am unable to change the original code
document.querySelector('button').addEventListener('click', function(){
setTimeout(function(){
document.querySelector('.old').classList = 'old new';
}, 100);
});
<div class="old">OLD</div>
<button>Click</button>
What I'd like to achieve is that once the new class is added then I want to change the TEXT line, the hack code as below
document.querySelector('button').addEventListener('click', function(){
setTimeout(function(){
document.querySelector('.old').classList = 'old new';
}, 100);
});
document.querySelector('button').addEventListener('click', function(){
if(document.querySelectorAll('.new').length !== 0) {
document.querySelector('.old').innerText = "123"
}
});
<div class="old">OLD</div>
<button>Click</button>
document.querySelector('button').addEventListener('click', function(){
if(document.querySelectorAll('.new').length !== 0) {
document.querySelector('.old').innerText = "123"
}
});
Since I am unable to know how long the timeout secs, so my first click won't work as it executes right away. So I have to add the timeout seconds bigger than the original. Is there an efficient way to detected if new class is added?
or timeout is the best solution for this use case? Thanks
You might use Mutation Observer for this purpose:
document.querySelector('button').addEventListener('click', function() {
setTimeout(function() {
document.querySelector('.old').classList = 'old new';
}, 500);
});
document.querySelector('button').addEventListener('click', function() {
var observer = new MutationObserver(function(mutationsList, observer) {
for (var mutation of mutationsList) {
if (
mutation.type === 'attributes' &&
mutation.attributeName === 'class' &&
mutation.target.classList.contains('new')
) {
mutation.target.innerText = "123";
observer.disconnect();
}
}
});
observer.observe(document.querySelector('.old'), {attributes: !0});
});
<div class="old">OLD</div>
<button>Click</button>
Use ClassList API to get this done. As new class will be add after some time. If new class is not added to dom then you can't get reference of this node to compare. The ClassList provides a handy method to solve this issue. use contains method to check if new class added or not. If added then change the text.
use below code
document.querySelector('button').addEventListener('click', function(){
if(document.querySelectorAll('.old')[0].classList.contains("new")) {
document.querySelector('.old').innerText = "123"
}
});
Solution
By using the GeckoWebBrowser in C# is there anyway to know when a page has completed updating its content using XMLHttpRequest ? I thought that the DocumentCompleted event would have done it but since the page it's not reloading it wont fire up...
You could use a mutation observer watching document.body's subtree, and assume that the page has finished being modified (say) 20ms after the last notification you get.
In JavaScript, that would look something like this:
(function() {
var observer;
// The click handler that appends content after a random delay
document.querySelector('input').addEventListener("click", function() {
if (!observer) {
hookupObserver();
}
setTimeout(function() {
var p = document.createElement('p');
p.innerHTML = "New content added at " + Date.now();
document.querySelector('div').appendChild(p);
}, 500 + Math.round(Math.random() * 500));
}, false);
// Watching for subtree mods to `document.body`:
function hookupObserver() {
var timer = 0;
observer = new MutationObserver(function() {
clearTimeout(timer);
timer = setTimeout(done, 40);
});
observer.observe(document.body, {childList: true, subtree: true});
}
function done() {
timer = 0;
alert("Modification complete");
}
})();
<input type="button" value="Click to simulate async modification">
<div>This is the page</div>
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)