Link to home
Start Free TrialLog in
Avatar of azaitchik
azaitchik

asked on

setTimeout and synchronization between frames

I am trying to write a general purpose JavaScript routine with which to synchronize between (invisible) frames that load data in a particular order. For example, frame B should not execute until frame A has completed and has set a flag to indicate as much. (Frame A has an accessor function to expose this state to the world.) I am trying to use setTimeout() in frames that must wait, and because the routine is generic the function which is rescheduled gets as parameters: the name of the waited-for frame (whose isDataLoaded() routine should be called), the real target to which the waiting frame should redirect when the wait is done, and a milliseconds-to-wait value. The idea is that the  top level frameset would set the SRC of frame B to this generic "SynchData.html" (passing in the above parameters) and then the synch routine would test-wait-test-redirect as appropriate.
Thus the FRAMESET looks like this:
<FRAMESET ROWS="2,2,*" BORDER=no>
    <FRAME NAME=UserDataFrame SRC="/ICTest/GetUserData.html">
    <FRAME NAME=ICDataFrame SRC="/ICTest/SynchData.html?WAITFOR=UserDataFrame&GOTONXT=/ICTest/GetICData.html" >
    <FRAME NAME=DispFrame SRC="/ICTest/testContent.html">
</FRAMESET>
What I am finding (using crude alert() calls as my debugging tool) is that the context seems lost and I get an error message that "UserDataFrame" is not defined right after I call the setTimeout call. I think that the parameters to this call might not be punctuated correctly, specifically with the correct number of double quotes, but I cannot tell. What's instructive to me is that I CAN access the isDataLoaded function and other test data in the UserDataFrame just a few lines earlier, as commented below. Maybe this is all real simple JavaScript syntax but I am quite confused.
Is it possible that on the recursive call i cannot refer to the other data frame as "top.UserDataFrame" even though the first time through I can? Doesn't the window hierarchy remain constant? (Also of note is the fact that in the real application the real targets of the data frames are not the HTML files but corresponding IDC and HTX files used to retrieve data from a database. So when (if) I get this simple version to work I will have to make sure that a frame can access data in anotehr frame after that frame has been redirected from its initial SRC (an html file) to an idc/htx page. The data I am interested in synchronizing with will be in the HTX file.)
Here is the JavaScript for the synch.html file. The other files are trivial and do work as they should. Thanks for any
help you can offer as I am quite stuck.
+=======================================================+
<HTML>
<HEAD>
<SCRIPT LANGUAGE=JavaScript>
// you can ignore the following function. It is here only
// because I use it below and I thought you might want to run this code
function getParamVal(pStringIn,pNameIn){
   // this function treats the search string as case insensitive, but
   // value returned is in the original case, of course
   var pString = pStringIn.toUpperCase();
   var pName = pNameIn.toUpperCase();
   var pNameLen = pName.length;
   var pNameLoc = pString.indexOf(pName);
   if (pNameLoc == -1) {
      return ('undefined');      
   }
   else {
      var nxtPLoc = pString.indexOf('&',pNameLoc+pNameLen+1);
      if (nxtPLoc == -1) {
         nxtPLoc = pString.length ;
      }
      return (pStringIn.substring(pNameLoc+pNameLen+1,nxtPLoc));
   }
}  

// here is where the relevant stuff is...
function waitForData(waitForFrameName,delay,goToTarget){
   alert('entering waitForData routine');
   // the following all succeed in accessing the UserDataFrame!
   var testStr1 = top.frames[0].testDatum1 ;
   alert('testStr1 is ' + testStr1);
   var testStr2 = eval('top.' + waitForFrameName + '.testDatum1') ;
   alert('testStr2 is ' + testStr2);
   var testStr3 = eval('top.' + waitForFrameName + '.isDataLoaded()') ;
   alert('testStr3 is ' + testStr3);

   var timeoutId = 'icNullStr';
 
   var reCall = 'waitForData(' + waitForFrameName + ',' + delay + ',' + goToTarget + ')' ;
   alert('reCall is ' + reCall) ;
   alert('goToTarget is ' + goToTarget);
   var isDataLoadedCall = 'top.' + waitForFrameName + '.isDataLoaded()';

   // note that in my tests the following evaluates false
   // rather than undefined.
   if ( typeof(eval(isDataLoadedCall))== 'undefined' || eval(isDataLoadedCall)== false ) {
      alert('isDataLoaded undefined or returned false... calling setTimeout');
      timeoutId = window.setTimeout(reCall,delay);
      // THE FOLLOWING IS LAST MESSAGE GENERATED SUCCESSFULLY
      // BY THIS ROUTINE. NEXT THING IS THE ERROR MSG
      // THAT UserDataFrame IS UNDEFINED.
      alert('timeoutId is ' + timeoutId);  
   }
   else {
      alert('checking timeoutId');
      if (timeoutId != 'icNullStr') {
         clearTimeout(timeoutId);  
      }
      alert('about to redirect');
      goToTarget = goToTarget.substring(1,goToTarget.length-1);
      alert('goToTarget after massage is ' + goToTarget);
      location = 'http://localhost' + goToTarget ;
   }

}

var paramString = window.location.search;
var waitForFrame=getParamVal(paramString,'WAITFOR');
alert('waitForFrame is ' + waitForFrame);
var goToParam=getParamVal(paramString,'GOTONXT');
// NEED TO ADD QUOTES OR CANNOT BE PASSED AS PARAMETER
// TO waitForData function, apparently!
var goToTarget =  '"' + goToParam + '"';
var goAhead = false ;
</SCRIPT>
</HEAD>

<BODY onLoad="waitForData(waitForFrame,50,goToTarget);">
</BODY>
</HTML>

Avatar of azaitchik
azaitchik

ASKER

Edited text of question
ASKER CERTIFIED SOLUTION
Avatar of tecbuilder
tecbuilder

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Wow. That was fast!
The thing is that I probably need a very generic way of indicating that frames B and C and D and ...
should not load until some other frame (A in my example) is done loading. I guess I can do something like onLoad="parent.FrameB.location=PAGEB;parent.FrameC.location=PAGEC;parent.FrameD.location=PAGED;", right?

One slight complication is that Frame A's html file really just parses some parameters and redirects to an IDX file which then asks for an HTX file. So the onLoad would have to be in the HTX file's BODY, I suppose. This makes it a bit messy to manage, as opposed to placing the synch logic in the SRC for the frames that need to wait, as I wanted to do.

You are right about the unpredictability of speeds. On the other hand I do want some way to time out or cancel a request, and so with the setTimeout approach I can at any rate abort after some number of calls. If page A fails to complete the logic in the HEAD section, frames B, C, D, etc will be left with their initial source (some null placeholder page, I suppose) and the user will not get any message, i.e. how would I detect this and generate a message?

Is there anything you spot in the JavaScript code I posted which strikes you as wrong? I may well adopt your suggestion in the end but I would like to know what was wrong. I didn't get what you meant by
>The problem that you are having is that Frame B does not exist >yet when it is being called.
Thanks.

Last questions first:

The page loaded into Frame B does not get loaded at the same time or before that of Frame A, so it doesn't exist, it will in a moment or 2, but when PageA makes it call to PageB within Frame B it doesn't.

You can use <frame onLoad="..."> rather than the <body onload="..."> as I mentioned in my answer.  However, using <frame onLoad="PageA;PageB,PageC,..."> will load several pages into several frames at one time.  Not after the first one is done, ie. Frame C will load at the same time as Frame B and not after Frame B has finished loading.

I didn't notice anything wrong in your code.

Your concern about values not getting set for your other pages can be taken care of by initializing some variables in the page that defines the frames.  For example:

In the page that defines the frames you have:

<script language="javascript">
<!--
var pageALoaded=false;
var pageBLoaded=false;
var pageCLoaded=false;
etc.
// -->
</script>

When PageA loads it would change the value of pageALoaded from false to true.  (This script would have to be just before </body>.  Now PageB gets loaded and a script runs to check and see if pageALoaded = true, if so it gets one set of data if not it gets another set of data.  PageB would also then change pageBLoaded from false to true.  The same thing would occur for all other pages as well.

The variables needed would be only those that are necessary and not for every page, ie. those page that others depend on.
I have done some tests using your suggestion and it seems to me (but I could be wrong) that there is a race condition between the redirect of Frames B,C,D which is executed by the onLoad routine for Frame A, on the one hand, and the SRC-ing of these frames in the original Frameset that creates the frames. At least thisis what I THINK is happening. Sometimes the frames redirect and sometimes they do not. I am wondering if they actually do (start to) redirect and THEN get sourced from the temporary html source I specify in the frameset that creates the frames? That temporary html file does not do any output but does reate a temporary data item in its header so I can see it using the View Source browser option. Possible?
We are leap frogging. I wrote my last note before your note appeared!  Hope you see this before replying to my last note!
I do not understand how your last note is compatible with your initial suggestion, so I must be missing something. You say
>The page loaded into Frame B does not get loaded at the same >time or before that of Frame A, so it doesn't exist, it will in >a moment or 2, but when PageA makes it call to PageB within >Frame B it doesn't.
But that is why I wanted to use setTimeout from within Frame B, so as to avoid this problem. On the contrary, your suggestion to
use the onLoad routine of Frame A to redirect frame B turns out
to fall prey to this problem. At least that is what I now believe to be happening. Maybe I am misunderstanding what you are suggesting I do. Using onLoad in the page which creates the frameset would not solve my problem either, I believe, as I need the data values retrieved into frame A in order to generate the right SQL query in frame B. That is one example of why I need to wait for A to complete before loading B (and C etc) from their real src.
Also, using the variables you indicate in your last reply doesn't help me since my question to begin with is not how to load alternate data values if A has not finished loading but rather how to WAIT until it has. That is why I wanted to use setTimeout, to wait for frame A to finish loading.
Are you suggesting that I go back to setTimeout and just synchronize using data in the topmost page (the one with the frameset) rather than in frame A itself?
Sorry to ask you to explain this all again but I just don't get it. Thanks!
All the frames should be defined at the same time.  The SRC within each frame gets executed as soon as the frame is defined. However, from what you are saying this doesn't seem to be true.  I always assumed that the SRC of a frame is always executed immediately after the frame is defined.

As for the data item in the temporary file, I don't understand what use it has to you.  It doesn't tell you if PageA or any other page was loaded.  It just tells you that that page is loaded.  However, you should be able to see the data item in the View Source.
I guess I want to start again and say: it clearly seems the right thing to do to have the frames which must wait on a resource, in this case the completion of some other frame's loading, do the test and wait, rather than have the frame being waited on try to trigger the loading of the (possibly many) frames which want to wait on it. So I go back to my original approach, which is to use setTimeout and test whether frame A has loaded by calling its isDataLoaded() accessor function. You point out that this may not be possible if, say, frame B gets to it before A has even started to load. So I will take your latest suggestion of having frames access logic in the page that defined the frameset which created these frames. THAT has to exist!
But I am still not sure that any of this has to do with my original problem since what I saw was that the isDataLoaded function WAS defined and in fact returned 'false' when called! The problem (UserDataFrame not defined) was signalled only after the call to setTimeout and, presumably, the RECALL of the routine that did the testing. And here what I suspect is that I may be passing the name of the frame (UserDataFrame) incorrectly, with or without some necessary quotes!
Still, I think you are right in suggesting that the top most frame be used to hold the completion flags, rather than UserDataFrame. And of course that frees me from having to pass the frame name altogether since it will always be just "top".
I will let you know what happens...
I guess I want to start again and say: it clearly seems the right thing to do to have the frames which must wait on a resource, in this case the completion of some other frame's loading, do the test and wait, rather than have the frame being waited on try to trigger the loading of the (possibly many) frames which want to wait on it. So I go back to my original approach, which is to use setTimeout and test whether frame A has loaded by calling its isDataLoaded() accessor function. You point out that this may not be possible if, say, frame B gets to it before A has even started to load. So I will take your latest suggestion of having frames access logic in the page that defined the frameset which created these frames. THAT has to exist!
But I am still not sure that any of this has to do with my original problem since what I saw was that the isDataLoaded function WAS defined and in fact returned 'false' when called! The problem (UserDataFrame not defined) was signalled only after the call to setTimeout and, presumably, the RECALL of the routine that did the testing. And here what I suspect is that I may be passing the name of the frame (UserDataFrame) incorrectly, with or without some necessary quotes!
Still, I think you are right in suggesting that the top most frame be used to hold the completion flags, rather than UserDataFrame. And of course that frees me from having to pass the frame name altogether since it will always be just "top".
I will let you know what happens...
I guess I want to start again and say: it clearly seems the right thing to do to have the frames which must wait on a resource, in this case the completion of some other frame's loading, do the test and wait, rather than have the frame being waited on try to trigger the loading of the (possibly many) frames which want to wait on it. So I go back to my original approach, which is to use setTimeout and test whether frame A has loaded by calling its isDataLoaded() accessor function. You point out that this may not be possible if, say, frame B gets to it before A has even started to load. So I will take your latest suggestion of having frames access logic in the page that defined the frameset which created these frames. THAT has to exist!
But I am still not sure that any of this has to do with my original problem since what I saw was that the isDataLoaded function WAS defined and in fact returned 'false' when called! The problem (UserDataFrame not defined) was signalled only after the call to setTimeout and, presumably, the RECALL of the routine that did the testing. And here what I suspect is that I may be passing the name of the frame (UserDataFrame) incorrectly, with or without some necessary quotes!
Still, I think you are right in suggesting that the top most frame be used to hold the completion flags, rather than UserDataFrame. And of course that frees me from having to pass the frame name altogether since it will always be just "top".
I will let you know what happens...
However, I think a simpler way to check to see if a page has loaded is a combination of what you are doing and my previous comment, ie. use a variable to determine if a page is loaded which would be set to false initially and true when a page has loaded (done by the page itself), and the setTimeout (to check to see if the variable is true every 2 seconds or so.  Here is what I came up with.  In PageA.html you may want to use something like:

<html>
<head>
<script language="JavaScritp>
<!--
var pageBLoaded=false;
var pageCLoaded=false;
var pageDLoaded=false;

function setPageVariable(sv) {
  sv=true
}

function checkLoadPage(pv,framenanme,pagename) {
  if (pv=false)
    timeoutId = window.setTimeout(checkLoadPage(pv,framename,pagename),2000)
  else
    parent.framename.location = pagename;
}

function loadPages() {
  for (var i=1; i<3; i++) {
    if (i=1) checkLoadPages(pageBLoaded,'frameB','PageB.html');
    else if (i=2) checkLoadPages(pageCLoaded,'frameC','PageC.html');
    else if (i=3) checkLoadPages(pageDLoaded,'frameD','PageD.html');
  }
}
// -->
</script>
</head>

<BODY onLoad="loadPages()">
</body>
</html>

This way when PageA.html is done loading the function loadPages will be called which will then load PageB.html, etc. sequentially.  PageB.html, etc. would have the script at the bottom of each page that eg.

<script language="javaScript">
<!--
// I believe that is how a variable is called in another frame.
// Have it done it in a long time.
  parent.frameA.setPageVariable(parent.frameA.pageBLoaded);
// -->
</script>
I am going to try to use your code fragments and see what progress I can make. But i still feel that the RIGHT way to do this is to place the check-wait-load logic inside each page that wants to wait, rather than in the page for which these pages are waiting. The reason is that although in a particular example it may be that all the waiting pages are waiting on the same single page, a more general case is where there are many different relationships involved. Also, it will be much more extensible to have a scheme whereby adding a new page with a dependency only requires me to code the new page, not recode the old page on which the new one wants to wait. I will let you know what happens.
I went back to my original idea of a generic synchronization routine used as the initial SRC for a page that must wait, with a redirect to get to the ultimate target after the waited for page is loaded. Using the top page (that defines the frameset) as the location for the arrays holding all synch related info worked out ok, although I think this is still restrictive in that at some point I may want pages located arbitrarily throughout the sire to be able to synch with frames anywhere. But for now it will do. Thanks for your help!