JavaScript - appendChild when an element starts existing - javascript

I am selecting multiple elements from within a page for a Chrome Extension and some of them are not being detected.
var innerChat = document.querySelector('.chat-list');
I want to appendChild to this element, but the script moves on to editing it before it even starts existing
innerChat.appendChild(emoteMenuWrap);
which obviously results in:
Uncaught TypeError: Cannot read property 'appendChild' of null
What would be the best approach for this?

Mutation observer is a good bet. For environments where this is not supported below also might do the trick since getElementsByClassName returns a live list:
var innerChat = document.getElementsByClassName("chat-list");
within your other script:
function process(){
if(!innerChat.length){
requestAnimationFrame(process);
} else {
innerChat[0].appendChild(emoteMenuWrap);
}
}
requestAnimationFrame(process);

Use a MutationObserver.
Suppose your innerChat has a parent element of chatDiv. chatDiv must exist on the page, but innerChat does not need to:
const observer = new MutationObserver(callbackFunction)
const config = { subtree: true };
observer.observe(chatDiv, config);
When the contents of chatDiv change (EG when you add innerChat to it,) observer will run the callback function.
Inside the callback function is where you would do whatever thing you wanted to wait to do.
One key to success here is to pick the parent container such that there aren't other changes going on inside it that would trigger your event more than once.
Finally, you'll probably want to remove the observer inside the callback:
observer.disconnect();
NB I edited this to suggest observing the subtree, since you're actually adding elements, not changing exiting ones.

Related

How to use the observer to check if a div is visible or not? (maybe Azure server problem?)

I need to check without further action if an element on my page is currently visible (this is being set either by button click or function call).
The observer seems to be the right tool from what I have read so far.
On my local machine everything works fine. But on my Azure test server I am getting the following error:
Uncaught TypeError: Failed to execute 'observe' on 'ResizeObserver': parameter 1 is not of type 'Element'
Here is the code I am using:
function setStatus() {
var target = $('#sidebar_container');
var ro = new ResizeObserver(() => {
if (target.is(':visible')) {
$("i.fa-user").addClass("active");
} else {
$("i.fa-user").removeClass("active");
}
});
// Observe element
ro.observe(target);
}
Is there something wrong with the code (although it's working on localhost) or is there a setting on the Azure server I would have to check?
From the code you posted, it looks like you are testing this functionality on localhost by resizing your window.
I'm saying this because, to check if an element has come into the viewport, you should use the Intersection Observer, not the Resize Observer.
You'll find a deep dive into how this observer works in the MDN link above.
To simply check if an element is inside the viewport (so it should be "visible") this is a possible solution:
// Get a reference for the target element
let element = document.querySelector('#sidebar_container');
// Create a function that will handle any intersection between some elements and the viewport.
let handleIntersection = function (entries) {
// Loop through all the observed elements
for (let entry of entries) {
// Check if the element is intersecting the viewport
if (entry.isIntersecting) {
console.log("The following element is visible in the viewport: ", entry.target);
// ...
}
}
}
const observer = new IntersectionObserver(handleIntersection);
observer.observe(element);
Also, you should pass to the observer an actual DOM element, not the jQuery wrapper. For this, it would be probably better to just use document.querySelector to select the element rather then jQuery.
In the devtools the $ sign is a shortcut to the querySelector, so if you were trying this code directly through the devtools, this might have triggered some confusion.

user script Javascript to change element value inside dynamically created iframe [duplicate]

Essentially I want to have a script execute when the contents of a DIV change. Since the scripts are separate (content script in the Chrome extension & webpage script), I need a way simply observe changes in DOM state. I could set up polling but that seems sloppy.
For a long time, DOM3 mutation events were the best available solution, but they have been deprecated for performance reasons. DOM4 Mutation Observers are the replacement for deprecated DOM3 mutation events. They are currently implemented in modern browsers as MutationObserver (or as the vendor-prefixed WebKitMutationObserver in old versions of Chrome):
MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
var observer = new MutationObserver(function(mutations, observer) {
// fired when a mutation occurs
console.log(mutations, observer);
// ...
});
// define what element should be observed by the observer
// and what types of mutations trigger the callback
observer.observe(document, {
subtree: true,
attributes: true
//...
});
This example listens for DOM changes on document and its entire subtree, and it will fire on changes to element attributes as well as structural changes. The draft spec has a full list of valid mutation listener properties:
childList
Set to true if mutations to target's children are to be observed.
attributes
Set to true if mutations to target's attributes are to be observed.
characterData
Set to true if mutations to target's data are to be observed.
subtree
Set to true if mutations to not just target, but also target's descendants are to be observed.
attributeOldValue
Set to true if attributes is set to true and target's attribute value before the mutation needs to be recorded.
characterDataOldValue
Set to true if characterData is set to true and target's data before the mutation needs to be recorded.
attributeFilter
Set to a list of attribute local names (without namespace) if not all attribute mutations need to be observed.
(This list is current as of April 2014; you may check the specification for any changes.)
Edit
This answer is now deprecated. See the answer by apsillers.
Since this is for a Chrome extension, you might as well use the standard DOM event - DOMSubtreeModified. See the support for this event across browsers. It has been supported in Chrome since 1.0.
$("#someDiv").bind("DOMSubtreeModified", function() {
alert("tree changed");
});
See a working example here.
Many sites use AJAX/XHR/fetch to add, show, modify content dynamically and window.history API instead of in-site navigation so current URL is changed programmatically. Such sites are called SPA, short for Single Page Application.
Usual JS methods of detecting page changes
MutationObserver (docs) to literally detect DOM changes.
Info/examples:
How to change the HTML content as it's loading on the page
Performance of MutationObserver to detect nodes in entire DOM.
Lightweight observer to react to a change only if URL also changed:
let lastUrl = location.href;
new MutationObserver(() => {
const url = location.href;
if (url !== lastUrl) {
lastUrl = url;
onUrlChange();
}
}).observe(document, {subtree: true, childList: true});
function onUrlChange() {
console.log('URL changed!', location.href);
}
Event listener for sites that signal content change by sending a DOM event:
pjax:end on document used by many pjax-based sites e.g. GitHub,
see How to run jQuery before and after a pjax load?
message on window used by e.g. Google search in Chrome browser,
see Chrome extension detect Google search refresh
yt-navigate-finish used by Youtube,
see How to detect page navigation on YouTube and modify its appearance seamlessly?
Periodic checking of DOM via setInterval:
Obviously this will work only in cases when you wait for a specific element identified by its id/selector to appear, and it won't let you universally detect new dynamically added content unless you invent some kind of fingerprinting the existing contents.
Cloaking History API:
let _pushState = History.prototype.pushState;
History.prototype.pushState = function (state, title, url) {
_pushState.call(this, state, title, url);
console.log('URL changed', url)
};
Listening to hashchange, popstate events:
window.addEventListener('hashchange', e => {
console.log('URL hash changed', e);
doSomething();
});
window.addEventListener('popstate', e => {
console.log('State changed', e);
doSomething();
});
P.S. All these methods can be used in a WebExtension's content script. It's because the case we're looking at is where the URL was changed via history.pushState or replaceState so the page itself remained the same with the same content script environment.
Another approach depending on how you are changing the div.
If you are using JQuery to change a div's contents with its html() method, you can extend that method and call a registration function each time you put html into a div.
(function( $, oldHtmlMethod ){
// Override the core html method in the jQuery object.
$.fn.html = function(){
// Execute the original HTML method using the
// augmented arguments collection.
var results = oldHtmlMethod.apply( this, arguments );
com.invisibility.elements.findAndRegisterElements(this);
return results;
};
})( jQuery, jQuery.fn.html );
We just intercept the calls to html(), call a registration function with this, which in the context refers to the target element getting new content, then we pass on the call to the original jquery.html() function. Remember to return the results of the original html() method, because JQuery expects it for method chaining.
For more info on method overriding and extension, check out http://www.bennadel.com/blog/2009-Using-Self-Executing-Function-Arguments-To-Override-Core-jQuery-Methods.htm, which is where I cribbed the closure function. Also check out the plugins tutorial at JQuery's site.
In addition to the "raw" tools provided by MutationObserver API, there exist "convenience" libraries to work with DOM mutations.
Consider: MutationObserver represents each DOM change in terms of subtrees. So if you're, for instance, waiting for a certain element to be inserted, it may be deep inside the children of mutations.mutation[i].addedNodes[j].
Another problem is when your own code, in reaction to mutations, changes DOM - you often want to filter it out.
A good convenience library that solves such problems is mutation-summary (disclaimer: I'm not the author, just a satisfied user), which enables you to specify queries of what you're interested in, and get exactly that.
Basic usage example from the docs:
var observer = new MutationSummary({
callback: updateWidgets,
queries: [{
element: '[data-widget]'
}]
});
function updateWidgets(summaries) {
var widgetSummary = summaries[0];
widgetSummary.added.forEach(buildNewWidget);
widgetSummary.removed.forEach(cleanupExistingWidget);
}

How to get the element in the DOM children array that returns in getElementsByTagName

Is there any way to detect the elements in proxy object that is returned from getElementsByTagName() ? I need to addEventListener to the returned svgs from below code
const svgs= document.getElementsByTagName('svg');
console.log('svgs',svgs);
const myArray = Array.from(svgs);
console.log('myArray ',myArray); // This is returning []
Below is the log that i can see in console . Can some one help me to get the svgs=>target=>HTMLCollection(49) into array
The reason is that these svg elements are apparently added to the document asynchronously. By the time you look at the console and open the svgs structure, the svg elements have been loaded, but this was not the case yet at the moment your code ran and created the array. That you see them is because of the console's lazy loading.
If the svg elements are loaded on page load, then you might be lucky and you can just wrap your code in something like:
window.onload = () => { /* your code */ }
But it is more likely that this content is loaded via some Ajax calls, and then the above wont help.
You could listen to DOM mutation events:
const svgs = document.getElementsByTagName('svg');
console.log(svgs.length); // <-- presumably outputs 0
const listener = new MutationObserver(updated);
// This listens to changes under the BODY element. If you can be more
// precise, then do so (to save resources). If you can do without
// the subtree option, then that is also preferred:
listener.observe(document.body, { childList: true, subtree: true });
function updated() {
console.log(svgs.length); // <-- notice the increased length
// Do whatever else you want to do with this collection.
// If you are sure you have all you need, then stop the listener:
listener.disconnect();
}
If the elements are populated all "at once", and you just want one call of the event listener, then you may still need to tune it with some debouncing pattern.

How can you tell when a dom node gets added to the document?

Is there any good way to get alerted when a dom node is added to the document? If you're putting together dom nodes in javascript, usually you put them together leaves-first, so that you're not reflowing all the time. But this means that when elements are added, they aren't visible on the page, which means certain information about them is inaccessible/wrong. For example offsetWidth will be 0 before its added to the document's flow, regardless of its width after its added.
So I would ideally like some way to get an event when a node is added to the document so I can then do things that require offsetWidth and other things like that that require it to be rendered.
Using a mutation observer to observe document should be a reasonable tool to use in most cases (it might not be reasonable if you're setting up a listener intended to listen for a long period of time):
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if(mutation.target.contains(block.domNode)) {
// do your thing, then..
observer.disconnect()
}
})
})
observer2.observe(document, {childList: true, subtree: true})
Note that if the dom node is removed from the document, then reinserted, the above code will not catch that (but could be modified to).

How to declare a document.on() listener to change text of specific selected elements using jQuery?

I try to define my problem clearly using a use case, so here it is:
consider <a class="fruit" href=#>apple</a> and instead of apple could be any fruit. i want to declare a jQuery on listener like the one below that any time a dom element that has fruit class created and added any part of the body my listener do something to that element.
$(document).on("HERE IS THE QUESTION",".fruit",function(){ do something to the fruit here });
I know there are some alternative ways for reaching the result but this is how i want to solve this problem due to some complexity reasons.
This only needs to work in Chrome.
If you're trying to monitor when a DOM element with a certain class name is created and added to the DOM, then you can't do that using straight jQuery as there is no good cross browser (that works in older browsers) way of doing that and it isn't something jQuery has tried to solve.
There is a newish interface (requires IE 11) called mutation observers (MDN reference here) that can allow you to monitor for certain types of DOM changes where you could get an event when items are added to the DOM and you could check if they were your class. If you really only need it to work in Chrome, this should be just what you want.
There is also the old standby (that's bad for battery life) technique of using a setInterval() to check the DOM regularly for changes.
Or, you could also describe in more detail and from a higher level what problem you're really trying to solve and we might be able to offer other ideas.
Here's some sample code using mutation objservers:
$(document).ready(function() {
// create an observer instance
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
for (var i = 0; i < mutation.addedNodes.length; i++) {
if (mutation.addedNodes[i].className === "fruit") {
console.log("found fruit", mutation.addedNodes[i]);
}
}
});
});
// configuration of the observer:
var config = { childList: true, subTree: true };
// pass in the target node, as well as the observer options
observer.observe($("#container")[0], config);
});
And, a working demo: http://jsfiddle.net/jfriend00/zxEXp/

Categories

Resources