I have an issue with DOMnode.getBoundingClientRect() because the data returned is not consistent. I am using windows.onload (for the lack of a better option) to trigger my function which takes a getBoundingClientRect().height of an element to do some calculation with it.
Issue is, the height of the element is not always consistent. window.onload even should fire last in the page lifecycle as far as i know and both the content and the stylesheets and external resources should be loaded by then, however the page apparently still hasn't rendered it properly yet since I sometimes get height values of like 400, 528, 630 and sometimes its correct (700) for an element I am trying to read.
Thoughts?
Is there some other way to detect when the a div (or a page) has had itself rendered fully?
window's load does indeed mean all resources (including stylesheets and images) have either loaded or failed, but:
It doesn't necessarily mean the browser has completely rendered the result, and
If other JavaScript code is running on load and changes things, those changes may also impact what you're seeing (of course)
Solving #2 will be specific to the code running, but if your problem is just #1, a short setTimeout should fix it:
window.onload = function() {
setTimeout(function() {
// Your code here
}, 60);
};
You may have to play with the delay. 0 tends to work for Chrome, but Firefox tends to want values in the at least 50-60ms range.
Or possibly just requestAnimationFrame may be sufficient:
window.onload = function() {
requestAnimationFrame(function() {
// Your code here
});
};
You might combine one or the other of the above with a check that document.readyState is "complete", since that's the very last thing the browser does (other than JavaScript code set to run on completion, of course). So for instance:
window.onload = function() {
function ready() {
// Your code here
}
function checkReady() {
if (document.readyState === "complete") {
ready();
} else {
setTimeout(checkReady, 0);
}
}
};
Related
I was under the impression that all DOM manipulations were synchronous.
However, this code is not running as I expect it to.
RecordManager.prototype._instantiateNewRecord = function(node) {
this.beginLoad();
var new_record = new Record(node.data.fields, this);
this.endLoad();
};
RecordManager.prototype.beginLoad = function() {
$(this.loader).removeClass('hidden');
};
RecordManager.prototype.endLoad = function() {
$(this.loader).addClass('hidden');
};
The Record constructor function is very large and it involves instantiating a whole bunch of Field objects, each of which instantiates some other objects of their own.
This results in a 1-2 second delay and I want to have a loading icon during this delay, so it doesn't just look like the page froze.
I expect the flow of events to be:
show loading icon
perform record instantiation operation
hide loading icon
Except the flow ends up being:
perform record instantiation operation
show loading icon
hide loading icon
So, you never even see the loading icon at all, I only know its loading briefly because the updates in the chrome development tools DOM viewer lag behind a little bit.
Should I be expecting this behavior from my code? If so, why?
Yes, this is to be expected. Although the DOM may have updated, until the browser has a chance to repaint, you won't see it. The repaint will get queued the same way as all other things get queued in the browser (ie it won't happen until the current block of JavaScript has finished executing), though pausing in a debugger will generally allow it to happen.
In your case, you can fix it using setTimeout with an immediate timeout:
RecordManager.prototype._instantiateNewRecord = function(node) {
this.beginLoad();
setTimeout(function() {
var new_record = new Record(node.data.fields, this);
this.endLoad();
}, 0);
};
This will allow the repaint to happen before executing the next part of your code.
JavaScript is always synchronous. It mimics multi-threaded behavior when it comes to ajax calls and timers, but when the callback gets returned, it will be blocking as usual.
That said, you most likely have a setTimeout in that constructor somewhere (or a method you're using does). Even if it's setTimeout(fnc, 0).
I'm prerendering my HTML pages for the search engines bots via PhantomJS through Selenium, so that they can see the fully loaded content. Currently, after PhantomJS reached the page, I'm waiting 5 seconds so that I'm sure everything is loaded.
Instead of waiting those 5 seconds every time, one solution I contemplate is to wait until an attribute html-ready on the <body /> tag is set to true:
<html ng-app>
<head>...</head>
<body html-ready="{{htmlReady}}">
...
</body>
</html>
.controller("AnyController", function($scope, $rootScope, AnyService) {
$rootScope.htmlReady = false;
AnyService.anyLongAction(function(anyData) {
$scope.anyData = anyData;
$rootScope.htmlReady = true;
});
})
The question is: will the html-ready attribute always be set to true after any view update has been done (e.g. displaying the anyData)? In other words, is it possible that during a laps, the html-ready attribute is true while the page is not fully loaded yet? If yes, how can it be handled?
It should be done after the digest, thus it has more chances to work as expected.
AnyService.anyLongAction(function(anyData) {
$scope.anyData = anyData;
$timeout(function () {
$rootScope.htmlReady = true;
}, 0, false);
});
But it is useless in terms of the app. You have to watch for changes in every single place, Angular doesn't offer anything to make the task easier.
Fortunately, you are free to abstract from Angular and keep it simple.
var ignoredElements = [];
ignoredElements = ignoredElements.concat($('.continuously-updating-widget').toArray());
var delay = 200; // add to taste
var timeout;
var ready = function () {
$('body').off('DOMSubtreeModified');
clearTimeout(timeoutLimit);
alert('ready');
};
$('body').on('DOMSubtreeModified', function (e) {
if (ignoredElements.indexOf(e.target) < 0) {
clearTimeout(timeout);
timeout = setTimeout(ready, delay);
}
});
var timeoutLimit = setTimeout(ready, 5000);
Feel free to angularify it if needed, though it isn't the production code anyway.
It is a good idea to put the handler into throttle wrapper function (the event will spam all the way). If you use remote requests on the page that can potentially exceed timeout delay, it may be better to combine this approach with several promises from async services and resolve them with $q.all. Still, much better than looking after every single directive and service.
DOMSubtreeModified is considered to be obsolete (it never was really acknowledged, MutationObserver is recommended instead), but current versions of FF and Chrome support it, and it should be ok for Selenium.
Short answer
No. It isn't guaranteed that your markup will be completely rendered when html-ready is set.
Long answer
To the best of my knowledge it's not possible to accurately determine when Angular has finished updating the DOM after the model changed. In general it happens very fast and it doesn't take more than a few cycles to finish, but that's not always the case.
Correctly detecting when a page has finished loading/rendering is actually quite a challenge, and if you take a look at the source code of specialized tools, like prerender, you'll see that they use several different checks in order to try to decide whether a page is ready or not. And even so it doesn't work 100% of the time (Phantom may crash, a request may take longer than usual to complete, and so on).
If you really want to come up with your own solution for this problem, I suggest that you take a look at prerender's source code (or another similar project) to get some inspiration.
To see the problem in action, see this jsbin. Clicking on the button triggers the buttonHandler(), which looks like this:
function buttonHandler() {
var elm = document.getElementById("progress");
elm.innerHTML = "thinking";
longPrimeCalc();
}
You would expect that this code changes the text of the div to "thinking", and then runs longPrimeCalc(), an arithmetic function that takes a few seconds to complete. However, this is not what happens. Instead, "longPrimeCalc" completes first, and then the text is updated to "thinking" after it's done running, as if the order of the two lines of code were reversed.
It appears that the browser does not run "innerHTML" code synchronously, but instead creates a new thread for it that executes at its own leisure.
My questions:
What is happening under the hood that is leading to this behavior?
How can I get the browser to behave the way I would expect, that is, force it to update the "innerHTML" before it executes "longPrimeCalc()"?
I tested this in the latest version of chrome.
Your surmise is incorrect. The .innerHTML update does complete synchronously (and the browser most definitely does not create a new thread). The browser simply does not bother to update the window until your code is finished. If you were to interrogate the DOM in some way that required the view to be updated, then the browser would have no choice.
For example, right after you set the innerHTML, add this line:
var sz = elm.clientHeight; // whoops that's not it; hold on ...
edit — I might figure out a way to trick the browser, or it might be impossible; it's certainly true that launching your long computation in a separate event loop will make it work:
setTimeout(longPrimeCalc, 10); // not 0, at least not with Firefox!
A good lesson here is that browsers try hard not to do pointless re-flows of the page layout. If your code had gone off on a prime number vacation and then come back and updated the innerHTML again, the browser would have saved some pointless work. Even if it's not painting an updated layout, browsers still have to figure out what's happened to the DOM in order to provide consistent answers when things like element sizes and positions are interrogated.
I think the way it works is that the currently running code completes first, then all the page updates are done. In this case, calling longPrimeCalc causes more code to be executed, and only when it is done does the page update change.
To fix this you have to have the currently running code terminate, then start the calculation in another context. You can do that with setTimeout. I'm not sure if there's any other way besides that.
Here is a jsfiddle showing the behavior. You don't have to pass a callback to longPrimeCalc, you just have to create another function which does what you want with the return value. Essentially you want to defer the calculation to another "thread" of execution. Writing the code this way makes it obvious what you're doing (Updated again to make it potentially nicer):
function defer(f, callback) {
var proc = function() {
result = f();
if (callback) {
callback(result);
}
}
setTimeout(proc, 50);
}
function buttonHandler() {
var elm = document.getElementById("progress");
elm.innerHTML = "thinking...";
defer(longPrimeCalc, function (isPrime) {
if (isPrime) {
elm.innerHTML = "It was a prime!";
}
else {
elm.innerHTML = "It was not a prime =(";
}
});
}
I'm working on a do-dad that can be embedded in a page like a youtube video. The particular effect I want needs jQuery to work.
I want to load jQuery on the condition that something on the page hasn't already added jQuery.
I though of testing
if (typeof($)=='function'){...
but that only works if jQuery is loaded & running by the time the page gets to my script. Since best practices these days is to embed you scripts in the footer, my embed code probably will never see jQuery most of the time anyway.
I thought of doing the test onready instead of onload, but the onready function is inside of jQuery. (I suppose I could use a standalone script? is there a good one?)
Lastly, I though of testing for jQuery after a timeout delay, but this seems inelegant at best and unreliable at worst.
Any thoughts?
Given your constraints, I see only two options:
Use window.load event:
(function() {
if (window.addEventListener) {
// Standard
window.addEventListener('load', jQueryCheck, false);
}
else if (window.attachEvent) {
// Microsoft
window.attachEvent('onload', jQueryCheck);
}
function jQueryCheck() {
if (typeof jQuery === "undefined") {
// No one's loaded it; either load it or do without
}
}
})();
window.load happens very late in the loading cycle, though, after all images are and such loaded.
Use a timeout. The good news is that the timeout can probably be quite short.
(function() {
var counter = 0;
doDetect();
function doDetect() {
if (typeof jQuery !== "undefined") {
// ...jQuery has been loaded
}
else if (++counter < 5) { // 5 or whatever
setTimeout(doDetect, 10);
}
else {
// Time out (and either load it or don't)
}
}
})();
You'll have to tune to decide the best values for the counter and the interval. But if jQuery isn't loaded even on the first or second loop, my guess (untested) is that it isn't going to be loaded...unless someone else is doing what you're doing. :-)
You can use window.onload. This fires after domReady, so jQuery would surely be loaded by this point.
And check for jQuery, not $. Sometimes people use jQuery with other libraries and use $ for something different.
However, IMHO, I don't think it's a big deal if jQuery gets loaded twice.
I've been using this code for to do this very thing for a while now. It also checks for a minimum version of jQuery (in our case, we're still using 1.4.2) before loading:
/* Checks if JQuery is loaded... if not, load it. */
/* Remember to update minimum version number when updating the main jquery.min.js file. */
if (typeof jQuery != 'undefined') {
/* jQuery is already loaded... verify minimum version number of 1.4.2 and reload newer if needed */
if (/1\.(0|1|2|3|4)\.(0|1)/.test(jQuery.fn.jquery) || /^1.1/.test(jQuery.fn.jquery) || /^1.2/.test(jQuery.fn.jquery)|| /^1.3/.test(jQuery.fn.jquery)) {
loadJQ();
}
} else {
loadJQ();
}
/* loads jQuery if not already loaded, or if not a recent enough version */
function loadJQ() {
/* adds a link to jQuery to the head, instead of inline, so it validates */
var headElement = document.getElementsByTagName("head")[0];
linkElement=document.createElement("script");
linkElement.src="../scripts/lib/jquery.min.js";
linkElement.type="text/javascript";
headElement.appendChild(linkElement);
}
I am having trouble with some JavaScript running before the page is completely rendered in IE 6 (maybe other versions too but just testing IE6 for now. Firefox seems to be OK). I can get around this by calling the js on window.onload like this:
window.onload = function(){doIt();}
However, my concern is the fact that I will overwrite anything else that may already be in window.onload. The code will be used as part of a library so I can not guarantee that window.onload will not be set somewhere else by someone else. I would rather append my function to the onload event like this:
window.onload += function(){doIt1();}
window.onload += function(){doIt2();}
But when I do so, only doit2() is called. Is there a way to register an event handler for when the page is fully rendered? My second thought would be to just put my code in a loop checking to make sure all my objects exist before running. But I am scared that this could potentially lockup the browser.
Just for some background info, my code is hiding/showing iFrames. I know that I can use the iFrame's onload attribute but I need all of the iFrames to be fully loaded before calling the code.
Any thoughts from the community? Thanks in advance for you input.
Use this generic addLoadEvent function...
function addLoadEvent(func) {
if(typeof window.onload != 'function')
window.onload = func;
else {
var oldLoad = window.onload;
window.onload = function() {
if(oldLoad) oldLoad();
func();
}
}
}
This essentially queues up functions to be executed. It never overwrites a previously assigned handler. Sample usage below...
addLoadEvent(function() { alert("One!"); });
addLoadEvent(two);
function two() {
alert("Two!");
}
I want to mention that libraries like jQuery take care of known issues like this for you.