How to know when an Html Element is ready (rendered)? - javascript

I have some javascript that adds an element to the document. Immediately after adding i am trying to obtain the element's clientWidth and clientHeight. However I am seeing all properties related to size to be 0. But at a later point i notice that the size properties do get populated with actual size values (eventually).
How can I know when the size details are available for an added Html element?
I have tried listening to various events, such as onload and onreadystatechange. However these events seem to only apply for the document itself, not for individual elements.
Thanks much.

DOM changes are postponed until the javascript thread quits, therefore this doesn't work:
someNode.addChild(someElem);
w = someElem.clientWidth // 0
You have to start a new thread in order to work with newly added element:
someNode.addChild(someElem);
setTimeout(function() {
w = someElem.clientWidth // should work
}, 0)
Alternatively (and better), try listening to a DOM mutation event:
document.body.addEventListener('DOMNodeInserted', function(e) {
alert('div height=' + e.target.clientHeight)
})
d = document.createElement("div")
d.appendChild(document.createTextNode("hello"))
t = document.body.appendChild(d)
http://jsfiddle.net/rM5cJ/

You could set an initial dictionary (object literal can be used)—
where you set at the very first start of the document , and after each html being rendered you can do :
<div id='aaa'> </div>
<Script>myObject.Add('aaa',1,doSomethingLater) </script>
You can not use the onload event since it's relevant only for : img / body/iframes.
The object can look like this :
myObject =
{
Add:function (a,b,cb){this[a]=b;cb(a,b)}
}

Related

Reset iframe's window object

I have an iframe, without any src specified.
I change the content dynamically with javascript using this code:
let doc = Paste.render_iframe.contentWindow.document
doc.open()
doc.write(Paste.get_value())
doc.close()
Which works. But the only problem is that if some javascript was used before, it stays inside the window object, and redefining them again causes errors.
I haven't found a way to reset/replace the iframe's window object.
What I did before was create a new iframe and replace the old one.
I could also try to change the src and then change the content.
The problem with those is that, 1) replacing iframes very often seems expensive, 2) I would have to deal with race conditions, having to check when iframe is fully loaded to accept new content.
So my question is, is there a way to replace or reset the iframe's window object without replacing it or modifying the src?
Guess I'm going to try changing the src dynamically and waiting for the load event to change the content.
Paste.setup_render = function()
{
Paste.render_iframe.addEventListener("load", function()
{
if(Paste.render_mode)
{
Paste.do_render()
}
})
}
Paste.reset_render_iframe = function()
{
Paste.render_iframe.src = `about:blank?t=${Date.now()}_${Paste.get_random_string(4)}`
}
Paste.render = function()
{
Paste.reset_render_iframe()
}
Paste.do_render = function()
{
let doc = Paste.render_iframe.contentWindow.document
doc.open()
doc.write(Paste.get_value())
doc.close()
}

Find out content has fully loaded after adding said content through Javascript

I'm implementing (through pure JavaScript) something like facebook's message loading. Meaning that if you scroll to the end of the page, new (HTML) content is loaded and added to the end of the page. I get this extra content through Ajax. But when I add this (HTML) to the page, it could mean the page has to load extra images. Is there a way I can figure out when the page has finished loading everything, so including all the new images?
You can add the load event (https://developer.mozilla.org/en-US/docs/Web/Events/load) to the appended elements:
function LOADER_FUNCTION(e) {
console.log("LOADED", e.target);
}
var element = document.querySelector('#parent');
for(i=0; i<element.childNodes.length; i++) {
element.childNodes[i].addEventListener('load', LOADER_FUNCTION);
}
This will call the LOADER_FUNCTION function when the content within that element is ready. Oddly this doesn't seem to work when attached to the parent.
Edit:
Here is a working example. Although this example is not using Ajax (mainly due to CORS issues) it should work under the same conditions. I've used innerHTML to set my DOM in order to demonstrate that this event is not limited to createElement -> appendChild:
(function(){
// This Code Runs after DOMContentLoaded
var element = document.getElementById('parent');
function LOADER_FUNCTION(e) {
document.querySelector('.status').innerText = ("LOADED " + e.target.tagName);
console.log("LOADED", e.target);
}
element.innerHTML = "<img src='http://i.imgur.com/OfSN9oH.jpg'>";
for(i=0; i<element.childNodes.length; i++){
// Attach new event handler
element.childNodes[i].addEventListener('load', LOADER_FUNCTION);
}
})()
<div class='status'></div>
<div id="parent"></div>
Edit 2:
For nested children in append/innerHTML use element.querySelectorAll('*');
https://jsfiddle.net/bckpL9k6/1/

Javascript: Changing "document.body.onresize" does not take hold without "console.log"

I'm writing a website with a canvas in it. The website has a script that runs successfully on every refresh except for a line at the end. When the script ends with:
document.body.onresize = function() {viewport.resizeCanvas()}
"document.body.onresize" is unchanged. (I double-checked in Chrome's javascript console: Entering "document.body.onresize" returns "undefined".)
However, when the script ends with:
document.body.onresize = function() {viewport.resizeCanvas()}
console.log(document.body.onresize)
"document.body.onresize" does change. The function works exactly as it should.
I can't explain why these two functionally identical pieces of code have different results. Can anyone help?
Edit: As far as I can tell, "document.body" is referring to the correct "document.body". When I call console.log(document.body) just before I assign document.body.onresize, the correct HTML is printed.
Edit 2: A solution (sort of)
When I substituted "window" for "document" the viewport's "resizeCanvas" function was called without fail every time I resized the window.
Why does "window" work while "document" only works if you call "console.log" first? Not a clue.
Resize events: no go
Most browsers don't support resize events on anything other than the window object. According to this page, only Opera supported detecting resizing documents. You can use the test page to quickly test it in multiple browsers. Another source that mentions a resize event on the body element specifically also notes that it doesn't work. If we look at these bug reports for Internet Explorer, we find out that having a resize event fire on arbitrary elements was an Internet Explorer-only feature, since removed.
Object.observe: maybe in the future
A more general method of figuring out changes to properties has been proposed and will most likely be implemented cross-browser: Object.observe(). You can observe any property for changes and run a function when that happens. This way, you can observe the element and when any property changes, such as clientWidth or clientHeight, you will get notified. It currently works only in Chrome with the experimental Javascript flag turned on. Plus, it is buggy. I could only get Chrome to notify me about properties that were changed inside Javascript, not properties that were changed by the browser. Experimental stuff; may or may not work in the future.
Current solution
Currently, you will have to do dirty checking: assign the value of the property that you want to watch to a variable and then check whether it has changed every 100 ms. For example, if you have the following HTML on a page:
<span id="editableSpan" contentEditable>Change me!</span>
And this script:
window.onload = function() {
function watch(obj, prop, func) {
var oldVal = null;
setInterval(function() {
var newVal = obj[prop];
if(oldVal != newVal) {
var oldValArg = oldVal;
oldVal = newVal;
func(newVal, oldValArg);
}
}, 100);
}
var span = document.querySelector('#editableSpan');
// add a watch on the offsetWidth property of the span element
watch(span, "offsetWidth", function(newVal, oldVal) {
console.log("width changed", oldVal, newVal);
});
}
This works similarly to Object.observe and for example the watch function in the AngularJS framework. It's not perfect, because with many such checks you will have a lot of code running every 100 ms. Additionally any action will be delayed 100 ms. You could possibly improve on this by using requestAnimationFrame instead of setInterval. That way, an update will be noticed whenever the browser redraws your webpage.
What you can do is that if you know for certain what particular action triggers a resize on your element that doesn't resize the full window you can trigger a resize event so your browser recalculate all of the divs (if by the case the browser is not triggering the event correctly).
With Jquery:
$(window).trigger('resize');
In the other hand, if you have an action that resizes an element you can always hold from that action to handle other following logic.
<script>
function body_OnResize() {
alert('resize');
}
</script>
<body onresize="body_OnResize()"></body>

Image width traces zero with onload when cached

I'm building a Javascript lightbox and I'm trying to adjust the size once the image has loaded. I'm using the code below, which works fine - it outputs the correct width once loaded.
My problem:
When I refresh, it will load the image instantly from the cache, and it seems to bypass the load. I get an instant zero for the width. Why does this happen?
My code:
var oImage = new Image();
oImage.src = 'http://mydomain.com/image.png';
container.html(oImage);
oImage.onload = function(){
alert(this.width);
}
** Update **
#Alex: This is the code I've tried with your plugin, I assume I'm probably doing something wrong. I'd be eager to get this working because your plugin looks quite good.
container.waitForImages(function() {
var cWidth = $(this).width();
alert("width: "+cWidth); // returns 0 - works first time but not cached
});
// Adding the image to the container for preload
container.html('<img src="mygraphic.png" />');
You need to do a few things...
Check the complete property of the img element.
Attach the load event before setting the src property.
Also, I found creating a new Image and assigning the src there is the best way to determine if the image has loaded or not.
You may want to switch the .html() and the .onload() calls.
If the image is loading from cache, I'm imagining that the .html() call completes before the script has had a chance to attach a function handler to the image's onload event. Therefore, effectively bypassing the load event itself (as the image has already loaded).
If it's still downloading the image (i.e. not cached), there will be more than enough time to call the .onload attach before the image completely finishes rendering.
While you're at it, you may want to do this the jQuery way, just so you're attaching events more similarly to DOM2 than DOM0.
var image = $('<img/>', {
src : 'http://mydomain.com/image.png'
}).load(function () {
alert(this.width);
})
// maybe clear container before if you want
.appendTo(container);
If we're going to have to set the src after the onload, we might as well do this instead:
var image = $('<img/>')
.load(function () {
alert(this.width);
})
.attr('src','http://mydomain.com/image.png')
.appendTo(container)
;
Hopefully that works cleanly.
This answer JavaScript: Know when an image is fully loaded suggests that you should set onload before setting src

How to check if an embedded SVG document is loaded in an html page?

I need to edit (using javascript) an SVG document embedded in an html page.
When the SVG is loaded, I can access the dom of the SVG and its elements. But I am not able to know if the SVG dom is ready or not, so I cant' perform default actions on the SVG when the html page is loaded.
To access the SVG dom, I use this code:
var svg = document.getElementById("chart").getSVGDocument();
where "chart" is the id of the embed element.
If I try to access the SVG when the html document is ready, in this way:
jQuery(document).ready( function() {
var svg = document.getElementById("chart").getSVGDocument();
...
svg is always null. I just need to know when it is not null, so I can start manipulate it.
Do you know if there is a way to do it?
On your embedding element (e.g 'embed', 'object', 'iframe') in the main document add an onload attribute which calls your function, or add the event listener in script, e.g embeddingElm.addEventListener('load', callbackFunction, false). Another option might be to listen for DOMContentLoaded, depends on what you want it for.
You can also add a load listener on the main document. jQuery(document).ready doesn't mean that all resources are loaded, just that the document itself has a DOM that is ready for action. However note that if you listen for load on the entire document your callback function won't be called until all resources in that document are loaded, css, javascript etc.
If you use inline svg, then jQuery(document).ready will work just fine however.
On a further note you might want to consider using embeddingElm.contentDocument (if available) instead of embeddingElm.getSVGDocument().
You could use an onload event for the check.
Suppose some.svg is embedded in object tag :
<body>
<object id="svgholder" data="some.svg" type="image/svg+xml"></object>
</body>
Jquery
var svgholder = $('body').find("object#svgholder");
svgholder.load("image/svg+xml", function() {
alert("some svg loaded");
});
javascript
var svgholder = document.getElementById("svgholder");
svgholder.onload = function() {
alert("some svg loaded");
}
Assuming your SVG is in an <embed> tag:
<embed id="embedded-image" src="image.svg" type="image/svg+xml" />
The SVG image is essentially in a sub-document that will have a separate load event to that of the main document. However, you can listen for this event and handle it:
var embed = document.getElementById("embedded-image");
embed.addEventListener('load', function()
{
var svg = embed.getSVGDocument();
// Operate upon the SVG DOM here
});
This is better than polling as any modification you make to the SVG will happen before it is first painted, reducing flicker and CPU effort spent painting.
The load event of the embedding element (e.g. object) would be my preference but, if that isn't a viable solution for some reason, the only generic and reliable test I've found for SVG DOM ready is the getCurrentTime method of the SVG root element:
var element = document.getElementById( 'elementId' );
var svgDoc = element.contentDocument;
var svgRoot = svgDoc ? svgDoc.rootElement : null;
if ( svgRoot
&& svgRoot.getCurrentTime
&& ( svgRoot.getCurrentTime() > 0 ))
{
/* SVG DOM ready */
}
The W3C SVG recommendation states that getCurrentTime on an SVGSVGElement:
Returns the current time in seconds relative to the start time for the current SVG document fragment. If getCurrentTime is called before the document timeline has begun (for example, by script running in a ‘script’ element before the document's SVGLoad event is dispatched), then 0 is returned.
Using jQuery you can bind to the window load event Erik mentions with:
$(window).load(function(){
var svg = document.getElementById("chart").getSVGDocument();
});
For future reference: an Angular(8)/Typescript solution looks like this:
#ViewChild('startimage', {static:false})
private startimage: ElementRef;
...
this.startimage.nativeElement.addEventListener('load', () => {
alert('loaded');
});
You can get to the svg by const svg = this.startimage.nativeElement.getSVGDocument();
You can assign an onload event handler to an element within your SVG document and have it call a javascript function in the html page. onload maps to SVGLoad.
http://www.w3.org/TR/SVG11/interact.html#LoadEvent
The event is triggered at the point at which the user agent has fully parsed the element and its descendants and is ready to act appropriately upon that element
You could try polling every so often.
function checkReady() {
var svg = document.getElementById("chart").getSVGDocument();
if (svg == null) {
setTimeout("checkReady()", 300);
} else {
...
}
}

Categories

Resources