There is a JavaScript function, of which I have zero control of the code, which calls a function that I wrote. My function uses DOM to generate an iFrame, defines it's src and then appends it to another DOM element. However, before my function returns, and thus allows continued execution of the containing function, it is imperative that the iFrame be fully loaded.
Here are the things that I have tried and why they do not work :
1. The SetTimeout option :
99.999% of the time, this is THE answer. As a matter of fact, in the past decade that I have been mentoring in JavaScript, I have always insisted that code could always be refactored to use this option, and never believed a scenario existed where that was not the case. Well, I finally found one! The problem is that because my function is being called inline, if the very next line is executed before my iFrame finishes loading, it totally neuters my script, and since the moment my script completes, the external script continues. A callback of sorts will not work
2. The "Do nothing" loop :This option you use while(//iFrame is not loaded){//do nothing}. In theory this would not return until the frame is loaded. The problem is that since this hogs all the resources, the iFrame never loads. This trick, although horribly unprofessional, dirty etc. will work when you just need an inline delay, but since I require an external thread to complete, it will not.In FF, after a few seconds, it pauses the script and an alert pops up stating that there is an unresponsive script. While that alert is up, the iFrame is able to load, and then my function is able to return, but having the browser frozen for 10 seconds, and then requiring the user to correctly dismiss an error is a no go.
3. The model dialogue :
I was inspired by the fact that the FF popup allowed the iFrame to load while halting the execution of the function, and thinking about it, I realized that it is because the modal dialogue, is a way of halting execution yet allowing other threads to continue! Brilliant, so I decided to try other modal options. Things like alert() work beautifully! When it pops up, even if only up for 1/10th of a second, the iFrame is able to complete, and all works great. And just in case the 1/10 of a second is not sufficient, I can put the model dialogue in the while loop from solution 2, and it would ensure that the iFrame is loaded in time. Sweet right? Except for the fact that I now have to pop up a very unprofessional dialogue for the user to dismiss in order to run my script. I fought with myself about this cost/benefit of this action, but then I encountered a scenario where my code was called 10 times on a single page! Having to dismiss 10 alerts before acessing a page?! That reminds me of the late 90s script kiddie pages, and is NOT an option.
4. A gazillion other delay script out there:There are about 10 jQuery delay or sleep functions, some of them actually quite cleverly developed, but none worked. A few prototype options, and again, none I found could do it! A dozen or so other libraries and frameworks claimed they had what I needed, but alas they all conspired to give me false hope.
I am convinced that since a built in model dialogue can halt execution, while allowing other threads to continue, there must be some code accessible way to do the same thing with out user input.
The Code is literally thousands upon thousands of lines and is proprietary, so I wrote this little example of the problem for you to work with. It is important to note the ONLY code you are able to change is in the onlyThingYouCanChange function
Test File :
<html>
<head>
</head>
</html>
<body>
<div id='iFrameHolder'></div>
<script type='text/javascript'>
function unChangeableFunction()
{
new_iFrame = onlyThingYouCanChange(document.getElementById('iFrameHolder'));
new_iFrame_doc = (new_iFrame.contentWindow || new_iFrame.contentDocument);
if(new_iFrame_doc.document)new_iFrame_doc=new_iFrame_doc.document;
new_iFrame_body = new_iFrame_doc.body;
if(new_iFrame_body.innerHTML != 'Loaded?')
{
//The world explodes!!!
alert('you just blew up the world! Way to go!');
}
else
{
alert('wow, you did it! Way to go!');
}
}
var iFrameLoaded = false;
function onlyThingYouCanChange(objectToAppendIFrameTo)
{
iFrameLoaded = false;
iframe=document.createElement('iframe');
iframe.onload = new Function('iFrameLoaded = true');
iframe.src = 'blank_frame.html'; //Must use an HTML doc on the server because there is a very specific DOM structure that must be maintained.
objectToAppendIFrameTo.appendChild(iframe);
var it = 0;
while(!iFrameLoaded) //I put the limit on here so you don't
{
//If I was able to put some sort of delay here that paused the exicution of the script, but did not halt all other browser threads, and did not require user interaction we'd be golden!
//alert('test'); //This would work if it did not require user interaction!
}
return iframe;
}
unChangeableFunction();
</script>
</body>
blank_frame.html :
<html>
<head>
</head>
<body style='margin:0px'>Loaded?</body>
</html>
HERE IS THE ANSWER I MADE FROM COMBINING IDEAS FROM RESPONDERS! YOU GUYS ROCK!
new source of the function I was allowed to change :
function onlyThingYouCanChange(objectToAppendIFrameTo)
{
iFrameLoaded = false;
iframe=document.createElement('iframe');
iframe.onload = new Function('iFrameLoaded = true');
iframe.src = 'blank_frame.html'; //Must use an HTML doc on the server because there is a very specific DOM structure that must be maintained.
objectToAppendIFrameTo.appendChild(iframe);
var it = 0;
while(!iFrameLoaded) //I put the limit on here so you don't
{
if (window.XMLHttpRequest)
{
AJAX=new XMLHttpRequest();
}
else
{
AJAX=new ActiveXObject("Microsoft.XMLHTTP");
}
if (AJAX)
{
AJAX.open("GET", 'slow_page.php', false);
AJAX.send(null);
}
else
{
alert('something is wrong with AJAX!');
}
//If I was able to put some sort of delay here that paused the exicution of the script, but did not halt all other browser threads, and did not require user interaction we'd be golden!
//alert('test'); //This would work if it did not require user interaction!
}
return iframe;
}
slow_page.php :
<?
usleep(100000);//sleep for 1/10th of a second, to allow iFrame time to load without DOSing our own server!
?>
I do want to note that I stated that there was nothing outside of that function that I could change, and adding the php page did violate that "rule" but in may case I was able to do that. If I were not able to do that, I could have called blank_frame.html instead of slow_page.php, and it should have only ever needed to call it once (so 2 times per frame load) assuming that it responded in an identical amount of time as the iFrame load. If for some reason the iFrame load was slower, it might call it 2ce (a total of 3 calls to the server)
Yeah, the fact that javascript is single threaded really bites you here. You can use a synchronous ajax call to a purposefully slow page to emulate a sleep, but you aren't going to get the results you want. Why don't you just make sure that your IFrame is loaded before unchangeable function is called?
NB This is extremely hacky, and I wouldn't use it in any real-world situation. Among other potential issues, given sufficient traffic you could end up DDOSing yourself.
You could create sleep functionality by making non-asynchronous (A)JAX calls. In some older browsers this may freeze everything, but at least it won't require any kind of user response.
while (!iFrameLoaded)
{
if (XMLHTTPRequest) {
var request = new XMLHttpRequest();
} else {
var request = new ActiveXObject("Microsoft.XMLHTTP");
}
request.open('GET', 'anyoldfile.htm', false);
request.send();
// check if the iframe is loaded and set iFrameLoaded
}
What you really need is an event to be fired when the iFrame content has loaded. This is actually really easy because the page inside the iFrame has its own events and it can access scripts on the parent page. You will need to be able to change the contents of the iFrame though.
In your iFrame, you'll need this piece of code
// Use whichever DOMReady function you like, or window.onload would work
window.addEventListener('DOMContentLoaded', function() {
if (parent.window.myFunction) {
parent.window.myFunction();
}
}, false);
Then in your parent page, make a function called "myFunction" and put all the scripts you need to fire in there. This should work every time.
Edit: To get this to work you really need two functions. I'm assuming that's really not an option so we'll hack the one function to contain two functions and call the right part when we need it to.
function onlyThingYouCanChange(stringOrObject) {
function createIFrame(objectToAppendIFrameTo) {
// This comment represents all the code that appends your iFrame
}
function onIFrameReady() {
// This comment represents all the stuff you want to happen when the iFrame is ready
}
// The bones of it
if (stringOrObject === "iFrameLoaded") {
onIFrameReady();
} else {
createIFrame(stringOrObject);
}
}
The script in the iFrame should now be changed to something like this:
// Use whichever DOMReady function you like, or window.onload would work
window.addEventListener('DOMContentLoaded', function() {
if (parent.window.onlyThingYouCanChange) {
parent.window.onlyThingYouCanChange('iFrameLoaded');
}
}, false);
I haven't tested it, but in theory that should do it
A stupefyingly simple ;-} answer using XPCOM:
// Get instance of the XPCOM thread manager.
var threadManager=Components.classes['#mozilla.org/thread-manager;1'].getService(
Components.interfaces.nsIThreadManager);
// Release current thread.
function doThread() {threadManager.currentThread.processNextEvent(false);};
// Event enabled delay, time in ms.
function delay(time) {
var end;
var start=Date.now();
do {
end=Date.now();
doThread();
} while ((end-start) <= time);
}
Works in recent version of Firefox. Sorry no hope for Explorer!
A recursive function might help out in this case. just call the function until a global variable indicates that the frame is loaded
var iFrameStarted = false; //you need two global vars
var iFrameLoaded = false;
function onlyThingYouCanChange(objectToAppendIFrameTo)
{
if (iFrameLoaded=false) // if the frame has loaded then you are done. skip everything and return iframe
{ if (iFrameStarted = false) //otherwise start the frame if it has not been
{
iFrameStarted = true;
iframe=document.createElement('iframe');
iframe.onload = new Function('iFrameLoaded = true');
iframe.src = 'blank_frame.html'; //Must use an HTML doc on the server because there is a very specific DOM structure
objectToAppendIFrameTo.appendChild(iframe);
var it = 0;
for (i=0;i<10000;i++) {} //slow down execution so you are not recursing yourself to death
onlyThingYouCanChange(objectToAppendIFrameTo); //start the recursion process
}
else //the frame has been started so continue recursion until the frame loaded
{
for (i=0;i<10000;i++) {} //slow down execution so you are not recursing yourself to death
onlyThingYouCanChange(objectToAppendIFrameTo); recursively call your function until the frame is loaded
}
}
return iframe; //you only get here when all the recursions are finished
}
Why can you not modify the base code? For example, it could be fairly simple to change the core function from
function unChangeableFunction()
{
new_iFrame = onlyThingYouCanChange(document.getElementById('iFrameHolder'));
new_iFrame_doc = (new_iFrame.contentWindow || new_iFrame.contentDocument);
if(new_iFrame_doc.document)new_iFrame_doc=new_iFrame_doc.document;
new_iFrame_body = new_iFrame_doc.body;
if(new_iFrame_body.innerHTML != 'Loaded?')
{
//The world explodes!!!
alert('you just blew up the world! Way to go!');
}
else
{
alert('wow, you did it! Way to go!');
}
}
To something like this:
function unChangeableFunction()
{
var new_iFrame = onlyThingYouCanChange(document.getElementById('iFrameHolder'));
new_iFrame.onload = function()
{
new_iFrame_doc = (new_iFrame.contentWindow || new_iFrame.contentDocument);
if(new_iFrame_doc.document)new_iFrame_doc=new_iFrame_doc.document;
new_iFrame_body = new_iFrame_doc.body;
if(new_iFrame_body.innerHTML != 'Loaded?')
{
//The world explodes!!!
alert('you just blew up the world! Way to go!');
}
else
{
alert('wow, you did it! Way to go!');
}
};
}
If that doesn't work for you, how about a transparent modification of the original code? Compile it with Javascript Strands and use the built-in futures support to handle this. Note that Javascript 1.7 also supports continuations, but would require changing the code manually to use them.
Another solution that may not be applicable, depending on how much you have simplified the original code. You could set an onload handler, then throw an error, then call unChangeableFunction in your onload handler:
function onlyThingYouCanChange(objectToAppendIFrameTo)
{
// using global variable func_called
if (!func_called) {
func_called = true;
var iframe=document.createElement('iframe');
iframe.src = 'blank_frame.html';
iframe.id = 'myIframe';
iframe.onload = function() {
unChangeableFunction();
};
objectToAppendIFrameTo.appendChild(iframe);
throw new Error('not an error');
} else {
return document.getElementById('myIframe');
}
}
This function (like unChangeableFunction) will be called twice: once in the first instance, then again when the onload handler is triggered. The two different pathways reflect this.
Again, this is hacky, and a definite abuse of JS's error functionality.
you can use cookie and setTimeout like that:
in blank_frame.html add a script:
<script type="text/javascript">
function deleteCookie(cookie_name)
{
var cookie_date=new Date();
cookie_date.setTime(cookie_date.getTime()-1);
document.cookie=cookie_name+="=;expires="+cookie_date.toGMTString();
}
function setCookie(name,value,expires,path,domain,secure){
document.cookie=name+"="+escape(value)+((expires)?"; expires="+expires.toGMTString():"")+((path)?"; path="+path:"")+((domain)?"; domain="+domain:"")+((secure)?"; secure":"");
}
window.onload=function(){
setCookie('iframe_loaded','yes',false,'/',false,false);
}
</script>
Basically you're adding a cookie iframe_loaded with value yes.
IMO it's better to remove the cookie as you need to do the same if you'll reload the page.
You can as well set the domain in setCookie function call.
Now in main file we'll use setTimeout with function that will check if the cookie exists, if it does then the function will return iframe like in your code:
function onlyThingYouCanChange(objectToAppendIFrameTo)
{
function get_cookie(cookie_name){
var results = document.cookie.match('(^|;) ?'+cookie_name+'=([^;]*)(;|$)');
return results?unescape(results[2]):null;
}
function deleteCookie(cookie_name){
var cookie_date=new Date();
cookie_date.setTime(cookie_date.getTime()-1);
document.cookie=cookie_name+="=;expires="+cookie_date.toGMTString();
}
iFrameLoaded = false;
iframe=document.createElement('iframe');
iframe.onload = new Function('iFrameLoaded = true');
iframe.src = 'blank_frame.html'; //Must use an HTML doc on the server because there is a very specific DOM structure that must be maintained.
objectToAppendIFrameTo.appendChild(iframe);
var it = 0;
function checkiframe(){
if(get_cookie('iframe_loaded')=="yes"){
alert('iframe loaded');
deleteCookie('iframe_loaded');
return iframe;
}else{
setTimeout(checkiframe,1000);
}
}
checkiframe();
}
As a failsafe cookie is being deleted in this file as well.
Hopefully that will give you something to work with :)
Cheers
G.
Related
I'm building a dynamic website that loads all pages inside a "body" div via jquery's load(). The problem is I have a script looped with setInterval inside the loaded PHP page, the reason being I want the script loaded only when that page is displayed. Now I discovered that the scripts keep running even after "leaving" the page (loading something else inside the div without refresh) and if I keep leaving / returning the loops stack up flooding my server with GET requests (from the javascript).
What's a good way to unload all JS once you leave the page? I could do a simple dummy var to not load scripts twice, but I would like to stop the loop after leaving the page because it's causing useless traffic and spouting console errors as elements it's supposed to fill are no longer there.
Sorry if this has already been asked, but it's pretty hard to come up with keywords for this.
1) why don't you try with clearInterval?
2) if you have a general (main) function a( ) { ... } doing something you can just override it with function a() { }; doing nothing
3) if you null the references to something it will be garbage collected
no code provided, so no more I can do to help you
This really sounds like you need to reevaluate your design. Either you need to drop ajax, or you need to not have collisions in you method names.
You can review this link: http://www.javascriptkit.com/javatutors/loadjavascriptcss2.shtml
Which gives information on how to remove the javascript from the DOM. However, modern browsers will leave the code in memory on the browser.
Since you are not dealing with real page loads/unloads I would build a system that simulates an unload event.
var myUnload = (function () {
var queue = [],
myUnload = function () {
queue.forEach(function (unloadFunc) {
undloadFunc();
});
queue = [];
};
myUnload.add = function (unloadFunc) {
queue.push(unloadFunc);
};
return myUnload;
}());
The code that loads the new pages should just run myUnload() before it loads the new page in.
function loadPage(url) {
myUnload();
$('#page').load(url);
}
Any code that is loaded by a page can call myUnload.add() to register a cleanup function that should be run when a new page is loaded.
// some .js file that is loaded by a page
(function () {
var doSomething = function () {
// do something here
},
timer = setInterval(doSomething, 1000);
// register our cleanup callback with unload event system
myUnload.add(function () {
// since all of this code is isolated in an IIFE,
// clearing the timer will remove the last reference to
// doSomething and it will automatically be GCed
// This callback, the timer var and the enclosing IIFE
// will be GCed too when myUnload sets queue back to an empty array.
clearInterval(timer);
});
}());
Is there a definitive JavaScript method for checking whether or not a web page has loaded completely? Completely, meaning 100% complete. HTML, scripts, CSS, images, plugins, AJAX, everything!
As user interaction can effect AJAX, let's assume there is no further user interaction with the page, apart from the initial page request.
What you're asking for is pretty much impossible. There is no way to determine whether everything has loaded completely. Here's why:
On a lot of webpages, AJAX only starts once the onload (or DOMReady) event fires, making the method of using the onload event to see if the page has loaded impossible.
You could theoretically tell if the webpage was performing an AJAX request by overriding window.XMLHttpRequest, but you still couldn't tell if plugins like Flash were still loading or not.
On some sites, like Twitter.com, the page only loads once and simply makes AJAX requests to the server every time the user clicks a link. How do you tell if the page has finished loading on a page like that?
In fact, the browser itself can't be completely certain whether the page has completely finished loading, or whether it's about to make more AJAX requests.
The only way to know for sure that everything loaded is to have every single piece of code on the page that loads something tell your code that it has finished once it loads.
A hacky, incomplete solution: You could try overriding XMLHttpRequest with a function that wraps the existing XMLHttpRequest and returns it. That would allow you to tell if a AJAX event is currently taking place. However, that solution wouldn't work for seeing if plugins are loaded, and you wouldn't be able to tell the difference between AJAX events that are triggered at page load and AJAX requests that happen periodically, like the ones on Stack Overflow that change the Stack Exchange icon on the top-left if you have new notifications.
Try something like this:
(function(oldHttpRequest){
// This isn't cross-browser, just a demonstration
// of replacing XMLHttpRequest
// Keep track of requests
var requests_running = 0;
// Override XMLHttpRequest's constructor
window.XMLHttpRequest = function() {
// Create an XMLHttpRequest
var request = new oldHttpRequest();
// Override the send method
var old_send = request.send;
request.send = function () {
requests_running += 1;
old_send.apply(request, arguments);
};
// Wait for it to load
req.addEventListener("load", function() {
requests_running -= 1;
}, false);
// Return our modified XMLHttpRequest
return request;
};
window.addEventListener("load", function() {
// Check every 50 ms to see if no requests are running
setTimeout(function checkLoad() {
if(requests_running === 0)
{
// Load is probably complete
}
else
setTimeout(checkLoad, 50);
}, 50);
}, false);
})(window.XMLHttpRequest)
The:
window.onload
event will fire at this point.
window.onLoad = function(){
//Stuff to do when page has loaded.
}
or
<body onLoad="functionCall()">
Basically ADW and Keith answer the question, but I would suggest not to use window.onload but:
if (window.addEventListener) {
window.addEventListener("load", myfunction, false);
} else {
window.attachEvent("onload", myfunction);
}
function myfunction() {
...
}
Using a combination of window.onload, document.readyState, and callbacks for AJAX requests, you should be able to do what you want. Simply make sure the window has loaded, the DOM is ready for manipulation, and keep track of AJAX requests.
For AJAX in particular, depending on how many requests you make: Increment a variable each time you make a request, and when the variable === the total amount of requests, fire a function. If you don't happen to know the amount of AJAX requests, but know which one would be last, simply have a callback function fire when it finishes.
When all is set and true, fire a final function to do what you want, knowing everything should be loaded.
In regards to Flash and Silverlight applications (not sure if window.onload or document.ready keeps track of those), you could also record the amount of data loaded withing the application, and when the loaded data === the total data, have the application fire a function or increment a variable to the page.
window.onload = function() {
var time = window.setInterval(function() {
if(document.readyState == "interactive") {
increment();
window.clearInterval(time);
}
}, 250);
}
var total = 10, current = 0;
var increment = function() {
current += 1;
if(current === total) { weAreDone(); }
}
function weAreDone() {
// Everything should be done!
}
Here is the non intrusive js function I scripted, using events on load. In this case, I fire events on js script load as this is my js autoloader function, but you can just add event on other items using the same principle. Provided this script looks after js scripts loaded in a dedicated div tag.
function scriptLoaded(e) {
var oLoadedScript = e.target || e.srcElement;
alert ('loaded : ' + oLoadedScript.src);
return false;
}
/**
* Import js lib and fire function ControlData on events
* #param js_librairies
* #returns {Boolean}
*/
function init(){
// lib import
// Locate js in the div
var myscript_location = document.getElementById('js_script_goes_here');
// DEBUG
if (undefined == myscript_location)
alert('div not found');
else
alert('found div : ' + myscript_location);
// to prevent js script from catching in dev mode
var force_js_reload = "?version=1" ;
for (var i=0; i < js_librairies.length ; ++i) {
var my_script = document.createElement('script');
my_script.defer = false;
my_script.src = relative_path + js_librairies[i] + force_js_reload ;
my_script.type = 'text/javascript';
// DEBUG
my_script.onload = scriptLoaded;
myscript_location.appendChild(my_script);
}
return false;
}
/**
* Start non intrusive js
* #param func
*/
function addLoadEvent(func) {
var oldonload = window.onload;
if (typeof window.onload != 'function') {
window.onload = func;
} else {
window.onload = function() {
if (oldonload) {
oldonload();
}
func();
};
}
}
//ONLOAD
addLoadEvent(init);
function r(f){/in/(document.readyState)?setTimeout(r,9,f):f()}
Courtesy: Smallest DOMReady code, ever - Dustin Diaz
Update: And for IE
function r(f){/in/.test(document.readyState)?setTimeout('r('+f+')',9):f()}
P.S: window.onload is a very different thing
If I have a var on my main page, and have a worker thread trying to set this var, is there a way the page can access it? Assuming everything is synchronized?
var routeWorker = new Worker('getroute.js');
var checkPatrolRouteFoundTimer;
var rw_resultRoute;
var routeFound = false;
routeWorker.onmessage = function(e) {
rw_resultRoute = e.data.route;
routeFound = true;
}
function checkPatrolReady() {
if(!routeFound)
checkPatrolRouteFoundTimer = setTimeout("checkPatrolReady()", 1000);
}
function ForcePatrol(index) {
routeWorker.postMessage(index);
checkPatrolReady();
...
//do work on route
...
}
in this case, the var I'm talking about is rw_resultRoute, and I can see it get set correctly when debugging. But the only thing is that it's set in the worker thread, not in the page thread.
I flow through the ForcePatrol() method the way i'm expecting to, and it looks like the rw_resultRoute is being set, since routeFound evaluates to true after the worker finishes.
Technically, it doesn't make sense, since routeFound can be set by the worker and read by the page thread, but rw_resultRoute can only be accessed by the worker.
I truly hope this is possible, otherwise I don't see a purpose for worker threads other than showing alert() messages and updating page HTML.
I truly hope this is possible, otherwise I don't see a purpose for worker threads other than showing alert() messages and updating page HTML.
It is meant to handle processing that would normally lock up the browser. Great for crunching numbers for canvas and running hashing.
in this case, the var I'm talking about is rw_resultRoute, and I can see it get set correctly when debugging. But the only thing is that it's set in the worker thread, not in the page thread.
The worker is separate from the page that spawns it. Only way to pass data is through messaging. You need to send the data with postMessage and have the onMessage handle the result. If you are handling different things, set up a switch statement to handle the different message types.
I solved the problem. There was some synchronization I wasn't doing correctly. I was using the setTimeout in the wrong way.
var routeWorker = new Worker('getroute.js');
var checkPatrolRouteFoundTimer;
var rw_resultRoute;
var routeFound = false;
routeWorker.onmessage = function(e) {
rw_resultRoute = e.data.route;
routeFound = true;
}
function checkPatrolReady() {
if(routeFound) {
...
//do work on route
...
clearInterval(checkPatrolRouteFoundTimer);
} else {
// do any maint here?
}
}
function ForcePatrol(index) {
routeWorker.postMessage(index);
checkPatrolRouteFoundTimer = setInterval("checkPatrolReady()", 1000);
}
Any call to setTimeout/setInterval will flow through, and in the first example i was using setTimeout instead of setInterval.
In the new way, calling ForcePatrol will setup the timer, and checkPatrolReady() will evaluate the flag, doing the work and clearing the timer if it is true.
So there is indeed nothing fancy in getting the results from web workers, but I was essentially creating a race condition with the worker results.
I've got a sequence of Javascript function calls in a function I have defined to be executed when a web doc is ready. I expected them to be executed in sequence, as one ends the next begins, but the behaviour I see doesn't match up with that.
Additionally there is manipulation of the graphical components going on in between the calls (for example, I add in a checkpoint time to draw on a div on the page inbetween each of the mentioned calls) but those redraws aren't happening in sequence... they all happen at once.
I'm a bit of a n00b with the whole javascript-in-the-browser thing, is there an obvious mistake I'm making, or a good resource to go find out how to do this stuff?
Update - sample
// called onReady()
function init() {
doFirstThing();
updateDisplayForFirstThing();
doSecondThingWithAjaxCall();
updateDisplayForSecondThing();
...
reportAllLoaded();
}
IE won't update the display until the current script is finished running. If you want to redraw in the middle of a sequence of events, you'll have to break your script up using timeouts.
If you post some code we can help refactor it.
edit: here's a general pattern to follow.
function init() {
doFirstThing();
updateDisplayForFirstThing();
}
function updateDisplayForFirstThing() {
// existing code
...
// prepare next sequence
var nextFn = function() {
// does this method run async? if so you'll have to
// call updateDisplayForSecondThing as a callback method for the
// ajax call rather than calling it inline here.
doSecondThingWithAjaxCall();
updateDisplayForSecondThing();
}
setTimeout(nextFn, 0);
}
function updateDisplayForSecondThing() {
// existing code
...
// prepare next sequence
var nextFn = function() {
// continue the pattern
// or if you're done call the last method
reportAllLoaded();
}
setTimeout(nextFn, 0);
}
This can be fixed for many cases by using callbacks, especially with AJAX calls -- for example:
function doFirstThing(fn){
// doing stuff
if(typeof fn == 'function') fn();
}
function updateDisplayForFirstThing(){
// doing other stuff
}
function init(){
doFirstThing(updateDisplayForFirstThing);
}
Another option is to use return values:
function doFirstThing(fn){
// doing stuff
if(x) return true;
else return false;
}
function updateDisplayForFirstThing(){
// doing other stuff
return true;
}
function init(){
if(doFirstThing()){ updateDisplayForFirstThing(); }
}
setting timeouts to step through your code is not really a good way to fix this problem because you'd have to set your timeouts for the maximum length of time each piece of code could possibly take to execute.
However, you may still sometimes need to use a setTimeout to ensure the DOM has properly updated after certain actions.
If you end up deciding that you would like some JavaScript threading, check out the still being drafted Web Workers API. Browser support is hit and miss though the API is implemented in most modern web browsers.
Question: exactly how did you go about determining when the "doc is ready"? The DOMContentLoaded event isn't supported in IE I'm fairly certain... if you're in need of waiting for your document to load in its entirety you could use something like this:
var onReady = function(callback) {
if (document.addEventListener) {
document.addEventListener("DOMContentLoaded", callback, false);
return true;
} else if (document.attachEvent) {
var DOMContentLoaded = function() {
if (document.readyState === "complete") {
document.detachEvent("onreadystatechange", DOMContentLoaded);
onReady();
}
};
return true;
}
};
Then of course you'll need to develop a setTimeout testing for some flags state indicating the page is loaded upon completion before continuing the execution of the rest of your code... that or any number of other methods...
Or you could just include the script at the bottom of your body...
I'm just rambling though until you have some code to show us?
I'm working on a project that requires my user script be run on pages as they are rendered without executing any of the page's JavaScript. That is to say, we need to browse with JavaScript disabled.
I've encountered a problem though when I try to delay execution of a function within my script. Whenever I make a call to window.setTimeout, the function I pass in never gets executed.
I think maybe this function is actually getting called on unsafeWindow instead of window. Is there any workaround for this?
I should mention that calls to setTimeout work fine when JavaScript is enabled and everything else in my script is working fine without enabling JavaScript.
Thanks for your help!
Even though Greasemonkey JavaScript runs with elevated privileges, as Pointy said, setTimeout functions are appended to the page's JavaScript space -- wrapped in a closure as needed. (In normal operation, the Greasemonkey instance is often gone by the time any timers, it has set, fire.)
So, if the page's main JavaScript is disabled, the timer will never run.
Possible workarounds:
Use GM_xmlhttpRequest as a crude delay. You can setup a page that deliberately draws out its response. So code like:
GM_xmlhttpRequest
(
{
method: "GET",
url: "http://YourTestServer.com/DelayService.php?Seconds=2",
onload: function (response) {YourDelayedFunctionHere (); }
}
);
Would call a utility page that you set up to do the delay for you.
Use NoScript to disable all of the page's JavaScript except for the main page. For example, for page, YourSite.com/testpage.htm, which includes scripts from, say, *SpamGenerator.net... Allow scripts from YourSite.com but block them from SpamGenerator.net.
The window reference is still the page's window, just wrapped in the sandbox wrapper thing. When you call setTimeout on it you're still setting up something to be run by the page. I suppose that it must be the case that the browser won't fire those timeout events at all (or will just ignore the events) when Javascript is disabled.
this can be patched like this:
You can say NO to NoScript + setTimeout = failed
In greasemonkey.js: find [ injectScripts ]: function..... add our GM-api.....
Add this code:
sandbox.setTimeOut = function (callback, timeout, p1,p2,p3/*....*/){
var args = Array.prototype.slice.call(arguments,2);
return sandbox.window.setTimeout(function(){
return callback.apply(sandbox, args);
} ,timeout);
}
or
sandbox.setInterval = function (callback, timeout, p1,p2,p3/*....*/){
var args = Array.prototype.slice.call(arguments,2);
return sandbox.window.setInterval(function(){
return callback.apply(sandbox, args);
} ,timeout);
}
This code is working fine, I have used it since May 2010.
In user.js you can test it like this:
setTimeout(alert,1000, 'i am happy');
var loopid = setInterval(alert, 1000, 'I am happy again');
setTimeout(clearInterval, 5000, loopid);
var j=300;
for(;~j;j--){ //running perfectly!
setTimeout(alert, 1000+20*j, 'I am happy' )
}
Solution 2
sandbox.kk_setTimeout = function (func, timeout, repeat_type, p1,p2,p3/*....*/){
var callback = { k100: sandbox };
var args = Array.slice.call(arguments,3);
// repeat_type: 0=once 1=repeatng, after fired stopped 2=always repeat
if(repeat_type!=2){
callback.notify = function (timer){ func.apply(this.k100,args); }
var timerCC = Components.Constructor("#mozilla.org/timer;1", "nsITimer", 'initWithCallback');
var R = repeat_type?1:0;
} else {
callback.observe = function (subject, topic, data) { func.call(this.k100); };
var timerCC = Components.Constructor("#mozilla.org/timer;1", "nsITimer", 'init');
var R = 2;
}
return new timerCC(callback, timeout, R);
}
// now have to test it:
var test100 = kk_setTimeout(alert, 1000, 0, 'i am timer'); //running = setTimeout
var test100 = kk_setTimeout(alert, 1000, 2, 'i am timer'); //running = setInterval
test100.cancal() ; //clear it by cancel() method
kk_setTimeout(alert, 1000+20*j, 2, 'i am happy' );
var j=300;
for(;~j;j--){
kk_setTimeout(alert, 1000+20*j, 0, 'i am happy 2' );
}
//bug:
//this solution 2 running after about 3-8 times differently stop, why bug ? i don't know.
// you will fail to use many times(over 3-8 time) kk_timeout(); or using repeat_type = 2 after fired 3-8 times timeout
//or running total time max about 20-30 seconds stop
//--- this maybe stop by option in about:config -- about [max javascript run time]
china-kkmove patched
edit to add…
Sorry everyone,
There are still a few patches to the code that I forgot to write:
sandbox.window = sandbox._proto_; // add this line also to the solution 1#
This error just came to my mind this morning.