Simulating a click on a div added dynamically not existing in DOM - javascript

As I understand an ajax callback adds a new div to the html on my end. I can see this element added in the html however I can't access it through DOM for some reason. I tried calling it from the console in chrome and a js bookmarklet after the element was already visible/added to the html. I am not sure why the DOM is not picking it up but that's another question I guess...
This is the div:
<div class="recaptcha-checkbox-checkmark" role="presentation"></div>
And I used the below to grab a reference to it but 90% of the time it returns a null (occasionally, it actually returns a reference but I honestly don't know /understand why):
document.querySelector(".recaptcha-checkbox-checkmark");
I have been looking at different ways to bind to this new div so I can execute a click on it after it appears in the html/dom but I am having trouble putting this together as it seems you can bind to a click event but you can't bind to something like a shown event :/
So this is what I currently have but it doesn't work as (I guess) it's based on a click event and I am not actually clicking it?
var host = document.querySelector("head");
if(host){
var importJquery = function(){
var jquery = document.createElement('script');
jquery.setAttribute("src", "https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js")
host.appendChild(jquery);
}
importJquery();
var bindToCaptcha = function(){
var script = document.createElement("script");
script.innerHTML = '$(document).on("click", ".recaptcha-checkbox-checkmark", function(e) {alert("clicked: %o", this);});';
host.appendChild(script);
}
bindToCaptcha();
}
So just to be clear I want to identify the moment this div shows up in the html and execute a click on it but can't since I am missing a reference to it.
I was considering running a loop at an interval checking whether the div existed but I would rather like to stay away from that approach (and also I am not sure this would work because the DOM doesn't always seem to return a reference this div).
Any help greatly appreciated.

You can use the MutationObserver API to detect when a child is added to a parent element:
// 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();
Example taken from https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver#Example_usage

I couldn't get the reference because the element I was after was located within an iframe. I feel like this question would have been answered instantly by someone else had I mentioned the iframe in the first place but I am new to this so I wasn't even aware of it until I have done some further digging.

Related

How can I fire a script each time an element with a certain class is added to DOM?

I have a function to modify divs of a specific class, but the problem is these divs are being loaded and unloaded dynamically by a 3rd party WP plugin that I cannot modify, and there are no click actions I can bind to. These divs may appear and disappear at any given point in time, and they come inside a more complex set of elements. The plugin starts by loading one div on page load, and later, on click, it loads other divs into it. These inner divs are timed, and they disappear on timeout, loading another set of divs and so on. The ones I'm trying to modify don't come in the first set on click, so I can't bind to that click.
From my research here on StackOverflow and elsewhere I understand that the old method of using mutation events with something like DOMNodeInsertedis deprecated, and the mutation observer should be used, but I can't get my head around it.
I've tried to adapt two pieces of code I found in answers to other questions here, but it didn't work for me.
The logic I'm using is to watch the first wrapper div that gets loaded on page load .modal-survey-container, and then, whenever it gets new divs loaded into it, my function should fire if my actual target .msnumericanswer is present inside the subtree. So far I've tried this:
var $j = jQuery;
$j(document).ready(function callback(records) {
records.forEach(function (record) {
var list = record.addedNodes;
var l = list.length - 1;
for ( ; l > -1; l-- ) {
if (list[l].nodeClass === '.msnumericanswer') {
$j('.msnumericanswer').each(function(i, obj) {
var t = $j(obj).data('tooltip');
$j(obj).prepend('<div class="labels">' + t + '</div>');
});
console.log(list[l]);
}
}
});
setTimeout(5000);
});
var observer = new MutationObserver(callback);
var targetNode = document.querySelectorAll(".modal-survey-container");
observer.observe(targetNode, { attributes: true, childList: true, subtree: true });
This doesn't work and throws two errors in console saying:
ReferenceError: callback is not defined.
TypeError: records.forEach is not a function.
The alternative logic is to catch the creation of my target's parent div .survey_table, so I also tried this:
var $j = jQuery;
var myElement = $j('.survey_table')[0];
var observer = new MutationObserver(function(mutations) {
if (document.contains(myElement)) {
$j('.msnumericanswer').each(function(i, obj) { //my working jQuery
var t = $j(obj).data('tooltip'); //my working jQuery
$j(obj).prepend('<div class="labels">' + t + '</div>'); //my working jQuery
});
}
});
observer.observe(document, {attributes: true, childList: true, characterData: false, subtree:true});
This doesn't work either, but I see no errors inconsole.
What am I doing wrong here?

JavaScript: Listen for attribute change?

Is it possible in JavaScript to listen for a change of attribute value? For example:
var element=document.querySelector('…');
element.addEventListener( ? ,doit,false);
element.setAttribute('something','whatever');
function doit() {
}
I would like to respond to any change in the something attribute.
I have read up on the MutationObserver object, as well as alternatives to that (including the one which uses animation events). As far as I can tell, they are about changes to the actual DOM. I’m more interested in attribute changes to a particular DOM element, so I don’t think that’s it. Certainly in my experimenting it doesn’t seem to work.
I would like to do this without jQuery.
Thanks
You need MutationObserver, Here in snippet I have used setTimeout to simulate modifying attribute
var element = document.querySelector('#test');
setTimeout(function() {
element.setAttribute('data-text', 'whatever');
}, 5000)
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if (mutation.type === "attributes") {
console.log("attributes changed");
// Example of accessing the element for which
// event was triggered
mutation.target.textContent = "Attribute of the element changed";
}
console.log(mutation.target);
});
});
observer.observe(element, {
attributes: true //configure it to listen to attribute changes
});
<div id="test">Dummy Text</div>
Additionally, mutation.target property gives the reference to mutated/changed node.
This question is already answered, but I'd like to share my experiences, because the mutation observer did not bring me the insights in needed.
Note This is some kind of hacky solution, but for (at least) debugging purposes quite good.
You can override the setAttribute function of a particalar element. This way you can also print the callstack, and get an insight of "who" changed the attribute value:
// select the target element
const target = document.querySelector("#element");
// store the original setAttribute reference
const setAttribute = target.setAttribute;
// override setAttribte
target.setAttribute = (key: string, value: string) => {
console.trace("--trace");
// use call, to set the context and prevent illegal invocation errors
setAttribute.call(target, key, value);
};

How to scan dynamically added text with jQuery

I'm trying to scan all text on a page and selectively create tooltips on pieces of text. I have working code that does this, but it only works on text that's on the page when the DOM ready event fires. Since the .live() function has been deprecated, we're supposed to use .on(), but that function only applies to elements that existed when it was called. Delegate event handlers apply to both current and future elements, but require the bound event to bubble up to the parent and the load event doesn't bubble.
So how can I scan all text--or even all the elements for that matter--as it's dynamically loaded?
UPDATE:
Per Makaze's comment, I tried several approaches. This one seems closest so far, but not quite:
$('body').on('DOMNodeInserted', '*:not("script")', function(e){
console.dir(e.target); //drill in here, I can see the nodeType==3 nodes
//var find = $(e.target);
var nodes = flattenTree(e.target.childNodes, 0); //recursively get all child nodes
for(var i in nodes){
var elem = $(nodes[i]);
var parent = elem.parent();
var txt = elem.text();
if(txt!==undefined && !txt.match(/^\s*$/)){
var refs = txt.match(versePattern);
if(refs!==null){
//var i = 0;
console.log(refs); //I never see the text node here, but I see it above when I manually drill into e.target
The versePattern matches as I expect in the static version of this code (which is working correctly), so I don't think that's the issue. Also, the '*:not("script")' doesn't seem to work as I'm still seeing <script> tags, but that's a minor thing that I can deal with later.
The MutationObserver constructor is what you want. Bind it to the parent element or document and filter your mutations from there.
// select the target node
var target = document.querySelector('#some-id');
// create an observer instance
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
console.log(mutation.addedNodes);
});
});
// configuration of the observer:
var config = {
attributes: true,
childList: true,
characterData: true
};
// pass in the target node, as well as the observer options
observer.observe(target, config);
setTimeout(function() {
target.appendChild(document.createTextNode('There!'));
// later, you can stop observing
// observer.disconnect();
}, 1000);
<div id="some-id">Wait for it...</div>
Side note: You can use .on() on the document and use a selector to filter the targets similar to .delegate(): $(parentSelectors).on(types, childSelectors, function).
Just initialized all your textboxes that you want to put tooltip on your js file.
Sample;
//Initialize Tooltip
$('#Name').tooltip()
$('#Age').tooltip()
$('#Address').tooltip()

Is there 'element rendered' event?

I need to accurately measure the dimensions of text within my web app, which I am achieving by creating an element (with relevant CSS classes), setting its innerHTML then adding it to the container using appendChild.
After doing this, there is a wait before the element has been rendered and its offsetWidth can be read to find out how wide the text is.
Currently, I'm using setTimeout(processText, 100) to wait until the render is complete.
Is there any callback I can listen to, or a more reliable way of telling when an element I have created has been rendered?
The accepted answer is from 2014 and is now outdated. A setTimeout may work, but it's not the cleanest and it doesn't necessarily guarantee that the element has been added to the DOM.
As of 2018, a MutationObserver is what you should use to detect when an element has been added to the DOM. MutationObservers are now widely supported across all modern browsers (Chrome 26+, Firefox 14+, IE11, Edge, Opera 15+, etc).
When an element has been added to the DOM, you will be able to retrieve its actual dimensions.
Here's a simple example of how you can use a MutationObserver to listen for when an element is added to the DOM.
For brevity, I'm using jQuery syntax to build the node and insert it into the DOM.
var myElement = $("<div>hello world</div>")[0];
var observer = new MutationObserver(function(mutations) {
if (document.contains(myElement)) {
console.log("It's in the DOM!");
observer.disconnect();
}
});
observer.observe(document, {attributes: false, childList: true, characterData: false, subtree:true});
$("body").append(myElement); // console.log: It's in the DOM!
The observer event handler will trigger whenever any node is added or removed from the document. Inside the handler, we then perform a contains check to determine if myElement is now in the document.
You don't need to iterate over each MutationRecord stored in mutations because you can perform the document.contains check directly upon myElement.
To improve performance, replace document with the specific element that will contain myElement in the DOM.
There is currently no DOM event indicating that an element has been fully rendered (eg. attached CSS applied and drawn). This can make some DOM manipulation code return wrong or random results (like getting the height of an element).
Using setTimeout to give the browser some overhead for rendering is the simplest way. Using
setTimeout(function(){}, 0)
is perhaps the most practically accurate, as it puts your code at the end of the active browser event queue without any more delay - in other words your code is queued right after the render operation (and all other operations happening at the time).
This blog post By Swizec Teller, suggests using requestAnimationFrame, and checking for the size of the element.
function try_do_some_stuff() {
if (!$("#element").size()) {
window.requestAnimationFrame(try_do_some_stuff);
} else {
$("#element").do_some_stuff();
}
};
in practice it only ever retries once. Because no matter what, by the next render frame, whether it comes in a 60th of a second, or a minute, the element will have been rendered.
You actually need to wait yet a bit after to get the after render time. requestAnimationFrame fires before the next paint. So requestAnimationFrame(()=>setTimeout(onrender, 0)) is right after the element has been rendered.
In my case solutions like setTimeout or MutationObserver weren't totaly realiable.
Instead I used the ResizeObserver. According to MDN:
Implementations should, if they follow the specification, invoke
resize events before paint and after layout.
So basically the observer always fires after layout, thus we should be able to get the correct dimensions of the observed element.
As a bonus the observer already returns the dimensions of the element. Therefore we don't even need to call something like offsetWidth (even though it should work too)
const myElement = document.createElement("div");
myElement.textContent = "test string";
const resizeObserver = new ResizeObserver(entries => {
const lastEntry = entries.pop();
// alternatively use contentBoxSize here
// Note: older versions of Firefox (<= 91) provided a single size object instead of an array of sizes
// https://bugzilla.mozilla.org/show_bug.cgi?id=1689645
const width = lastEntry.borderBoxSize?.inlineSize ?? lastEntry.borderBoxSize[0].inlineSize;
const height = lastEntry.borderBoxSize?.blockSize ?? lastEntry.borderBoxSize[0].blockSize;
resizeObserver.disconnect();
console.log("width:", width, "height:", height);
});
resizeObserver.observe(myElement);
document.body.append(myElement);
This can also we wrapped in a handy async function like this:
function appendAwaitLayout(parent, element) {
return new Promise((resolve, reject) => {
const resizeObserver = new ResizeObserver((entries) => {
resizeObserver.disconnect();
resolve(entries);
});
resizeObserver.observe(element);
parent.append(element);
});
}
// call it like this
appendAwaitLayout(document.body, document.createElement("div")).then((entries) => {
console.log(entries)
// do stuff here ...
});
The MutationObserver is probably the best approach, but here's a simple alternative that may work
I had some javascript that built the HTML for a large table and set the innerHTML of a div to the generated HTML. If I fetched Date() immediately after setting the innerHTML, I found that the timestamp was for a time prior to the table being completely rendered. I wanted to know how long the rendering was taking (meaning I needed to check Date() after the rendering was done). I found I could do this by setting the innerHTML of the div and then (in the same script) calling the click method of some button on the page. The click handler would get executed only after the HTML was fully rendered, not just after the innerHTML property of div got set. I verified this by comparing the Date() value generated by the click handler to the Date() value retrieved by the script that was setting the innerHTML property of the div.
Hope someone finds this useful
suppose your element has classname class="test"
The following function continue test if change has occured
if it does, run the function
function addResizeListener(elem, fun) {
let id;
let style = getComputedStyle(elem);
let wid = style.width;
let hei = style.height;
id = requestAnimationFrame(test)
function test() {
let newStyle = getComputedStyle(elem);
if (wid !== newStyle.width ||
hei !== newStyle.height) {
fun();
wid = newStyle.width;
hei = newStyle.height;
}
id = requestAnimationFrame(test);
}
}
let test = document.querySelector('.test');
addResizeListener(test,function () {
console.log("I changed!!")
});
when you make for example
var clonedForm = $('#empty_form_to_clone').clone(true)[0];
var newForm = $(clonedForm).html().replace(/__prefix__/g, next_index_id_form);
// next_index_id_form is just a integer
What am I doing here?
I clone a element already rendered and change the html to be rendered.
Next i append that text to a container.
$('#container_id').append(newForm);
The problem comes when i want to add a event handler to a button inside newForm, WELL, just use ready event.
$(clonedForm).ready(function(event){
addEventHandlerToFormButton();
})
I hope this help you.
PS: Sorry for my English.
According to #Elliot B.'s answer, I made a plan that suits me.
const callback = () => {
const el = document.querySelector('#a');
if (el) {
observer.disconnect();
el.addEventListener('click', () => {});
}
};
const observer = new MutationObserver(callback);
observer.observe(document.body, { subtree: true, childList: true });

jQuery watch for domElement changes?

I have an ajax callback which injects html markup into a footer div.
What I can't figure out is how to create a way to monitor the div for when it's contents change. Placing the layout logic I'm trying to create in the callback isn't an option as each method (callback and my layout div handler) shouldn't know about the other.
Ideally I'd like to see some kind of event handler akin to $('#myDiv').ContentsChanged(function() {...}) or $('#myDiv').TriggerWhenContentExists( function() {...})
I found a plugin called watch and an improved version of that plugin but could never get either to trigger. I tried "watching" everything I could think of (i.e. height property of the div being changed via the ajax injection) but couldn't get them to do anything at all.
Any thoughts/help?
The most effective way I've found is to bind to the DOMSubtreeModified event. It works well with both jQuery's $.html() and via standard JavaScript's innerHTML property.
$('#content').bind('DOMSubtreeModified', function(e) {
if (e.target.innerHTML.length > 0) {
// Content change handler
}
});
http://jsfiddle.net/hnCxK/
When called from jQuery's $.html(), I found the event fires twice: once to clear existing contents and once to set it. A quick .length-check will work in simple implementations.
It's also important to note that the event will always fire when set to an HTML string (ie '<p>Hello, world</p>'). And that the event will only fire when changed for plain-text strings.
You can listen for changes to DOM elements (your div for example) by binding onto DOMCharacterDataModified tested in chrome but doesn't work in IE see a demo here
Clicking the button causes a change in the div which is being watched, which in turn fills out another div to show you its working...
Having a bit more of a look Shiki's answer to jquery listen to changes within a div and act accordingly looks like it should do what you want:
$('#idOfDiv').bind('contentchanged', function() {
// do something after the div content has changed
alert('woo');
});
In your function that updates the div:
$('#idOfDiv').trigger('contentchanged');
See this as a working demo here
There is a neat javascript library, mutation-summary by google, that lets you observe dom changes concisely. The great thing about it, is that if you want, you can be informed only of the actions that actually made a difference in the DOM, to understand what I mean you should watch the very informative video on the project's homepage.
link:
http://code.google.com/p/mutation-summary/
jquery wrapper:
https://github.com/joelpurra/jquery-mutation-summary
You might want to look into the DOMNodeInserted event for Firefox/Opera/Safari and the onpropertychange event for IE. It probably wouldn't be too hard to utilize these events but it might be a little hack-ish. Here is some javascript event documentation: http://help.dottoro.com/larrqqck.php
Now we can use a MutationObserver ; Well, apparently we must.
Use of Mutation Events is deprecated. Use MutationObserver instead.
jquery.min.js:2:41540
From https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver :
// Select the node that will be observed for mutations
const targetNode = document.getElementById('some-id');
// 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.');
}
else if (mutation.type === 'attributes') {
console.log('The ' + mutation.attributeName + ' attribute was modified.');
}
}
};
// Create an observer instance linked to the callback function
const observer = new MutationObserver(callback);
// Start observing the target node for configured mutations
observer.observe(targetNode, config);
// Later, you can stop observing
observer.disconnect();

Categories