Remote ExecuteRemote

Workarounds and usability notes.

Remote ExecuteRemote

Postby John Robin Dove » Fri Dec 04, 2020 12:01 pm

Hi Clifton,

I am getting slowly nearer to completing my project but things are getting complicated. My program now starts in a main window which fills the browser. Using pgGotoURL I reveal other parts of the program. Each one is an exported tbk. They are shown in two different fields. I use fadeObject to transition from one field to the other when necessary. The part being loaded calls a function in the main window when it has finished loading. This all works as planned. The problem is that the parts being loaded also use pgGotoURL to load other parts (HTML files Toolbook or non-Toolbook). These were used via pgExecuteRemote. The best solution is no doubt to put as many as possible into the main window but it will take some time to alter everything. Is there any way that some of them could be accessed in their current state? For example the video 'module' loads another exported tbk called "details". When the video module was in the main window the two parts communicated easily via pgExecuteRemote. Now that the video module is not in the main window that is no longer possible. How could I call functions in the sub-sub-Windows? If it's not possible I'll just have to rebuild everything.

John
John Robin Dove
 
Posts: 486
Joined: Thu Jan 23, 2014 4:35 am

Re: Remote ExecuteRemote

Postby Clifton » Fri Dec 04, 2020 1:55 pm

pgExecuteRemote() works by communicating back and forth between a "mainWindow" application and its child windows. If two child window must talk to each other, then you must send the request to the "mainWindow" and then to the child window and back again. While this sounds a bit clunky, it respects browser security protocols.

To automate this and make things relatively easy, write a script (sharedAction) in the "mainWindow" that handles all the requests. I've done this with our LearnToType.Today program and the communication is pretty reliable. It is not possible for me to supply this coding because it depends on what is going on between the windows.

Here are some basics to get you going:
<make id="sharedAction" type="div" level="0" dims="-10,0,1,1" class="" refObj="" autoAlign="" replace="">
    <style>
        { "display" : "none" }
    </style>
    <function name="myWinData" event="" params="targetName,funcCaller,args,callBackWin">
        <![CDATA[
            /* CLIFTON: Pass data back and forth between main and child windows.
                         This method is NOT asynchronous.
                    Parameters:
                    targetName = string as name of child window in which to execute [funcCaller]
                    funcCaller = string as name of tbfunction or other custom function to execute in [targetName]
                    args = array of arguments to pass to [funcCaller]
                    callBackWin = string as name of child window that will receive the data returned from
                            execution of [funcCaller] in [targetName]
            ***/


            //Request or send data
            var data = tbfunction_pgExecuteRemote(targetName, funcCaller, args, false);

            //Notify page in calling window by user event with [data] stored in [value] parameter
            tbfunction_pgExecuteRemote(callBackWin, 'tbfunction_pgTBObjSet', ['page', data], true);
        ]]>
    </function>
</make>

Consider this scenario:
Child window A requests the current page name from child window B.
  • tbfunction_pgExecuteRemote("main","tbfunction_pgTBObjSet", ["sharedAction","myWinData",
    ["childWindowB","tbfunction_getPageNameFromNum",["current"],"childWindowA"]],false);
  • Result: a user event will be sent to the current page in child window A. The [value] parameter of the user event should equal the name of the current page displayed in child window B.
  • While this assumes that all child windows are ToolBook exported content, this will also work if you use the PGSD_PowerPac_comlink.js file inside a non-native child window. The only exception is that you will have to have your main window execute a user defined function instead of tbfunction_pgTBObjSet(). You can open the "com_link.js" and read the comments section at the top of the file for information on what functions are included in the js file.

Other notes and considerations:
When building your function(s), please also note that cookies created using the PowerPac functions can be saved to the browser "session". The PowerPac cookie system is designed to always save its session cookies to the mainWindow session and not any of the child window. This means that any request for data stored in a session cookie, can be access by any child window at any time. This behavior may be useful to you as you plan and test how things should work in your case.
    Example:
    Child window A needs data from child window B. A request is sent to the mainWindow to request the data and the mainWindow pushes the request to child window B. Child window B then writes a cookie containing the data to a session cookie (using tbfunction_createCookie). Child window A retrieves the session cookie data (using tbfunction_readCookie) and deletes the session cookie since it is no longer needed.

    Whether this logic is practical for your application depends on the design of your application. In most cases, the previous code may be all that is needed.
Enjoy!
Clifton
Site Admin
 
Posts: 732
Joined: Tue Jan 14, 2014 1:04 am

Re: Remote ExecuteRemote

Postby John Robin Dove » Sat Dec 05, 2020 8:51 am

Thank you for your help. I'm still trying to get my head round some of this. I think in many cases I should be using the main window to execute global functions. For example all the translation should be done here. In a sub window, I send a line number via pgExecuteRemote to a function in the main window which finds the required line in an XML database and puts it into a session cookie which can then be retrieved in the embedded window. I had actually already used a function like this in one part of the program. It looks like this:
<function name="getTrans" event="" params="code">
<![CDATA[
tbfunction_pgExecuteRemote("main", "tbfunction_pgTBObjSet", ["sharedActions", "getText", code]);
var txt = "";
do
{
txt = tbfunction_readCookie("trans", "session");
}
while (txt == "")
return txt;
]]>
</function>

Do you think this is OK?

Here is another scenario. I have made this diagram to try and understand it better.
Image

The user has opened the details page in a sub-sub window and clicks a button to set the number of previews the student is allowed before doing the exercise. This number is sent to the main window which sends it on to the video module which updates the global variable sharedActions.previews. This variable can then be saved to a file if the users clicks on the appropriate button. Is this the right approach or am I missing something? I think it's going to take quite a while to reorganize everything but I suppose that after all this time a few more weeks or months won't make any difference.
John Robin Dove
 
Posts: 486
Joined: Thu Jan 23, 2014 4:35 am

Re: Remote ExecuteRemote

Postby Clifton » Sat Dec 05, 2020 11:55 am

Since Javascript is single-threaded, this code will potentially cause an infinite loop state:
    tbfunction_pgExecuteRemote("main", "tbfunction_pgTBObjSet", ["sharedActions", "getText", code]);
    var txt = "";
    do {
    txt = tbfunction_readCookie("trans", "session");
    }
    while (txt == "") return txt;
If your getText() function is asynchronous in waiting for the database to respond, then something like this might work:
    //Assuming this is the "details" window and it is waiting for a session cookie to be written
    tbfunction_pgExecuteRemote("main", "tbfunction_pgTBObjSet", ["sharedActions", "getText", code]);
    var THIS = this; //Copy current object reference to local reference
    var fct = function() {
        var txt = tbfunction_readCookie( "previews", "private"); //Read from "private" session (always DOM parent window & encrypted)
        if (pgIsNumber(txt)) {
            clearInterval( THIS.tmr || 0 ); //Stop the timer
            delete THIS.tmr;
            //valid txt value exists so do something with it
        } else {
            //If something fails to work we need a contingency
            THIS.tmr[1]++;
            if (THIS.tmr[1] >= 40) {
                clearInterval( THIS.tmr[0] || 0); //Stop the timer
                delete THIS.tmr;
                alert( "Could not retrieve your previews setting." );
            }
        }
    };
    this.tmr = [ setInterval( fct, 250 ), 0 ]; // [0] = timer id; [1] = interval counter to determine when to fail

There are probably lots of other ways to do this, but this may get you to another level of progress. It also helps you to see how to create a waiting loop that is browser-friendly. While there is no need to change your current code, you may want begin using "private" session cookies. They are already encrypted and stored in the DOM (top level parent window) just in case the browser rejects storing session cookies because of user preferences.
 
Clifton
Site Admin
 
Posts: 732
Joined: Tue Jan 14, 2014 1:04 am

Re: Remote ExecuteRemote

Postby John Robin Dove » Sat Dec 05, 2020 12:11 pm

Thanks Clifton.

Your solution looks less dangerous. Mine works but it seemed a bit dodgy to me.

Does this have to be an asynchronous function? I wrote it that way because it seemed being asynchronous was inevitable in this case. Is there any way it could synchronous?
John Robin Dove
 
Posts: 486
Joined: Thu Jan 23, 2014 4:35 am

Re: Remote ExecuteRemote

Postby Clifton » Sat Dec 05, 2020 1:25 pm

The only reason to use a interval timer is if the script is waiting for something to happen.
If your script is NOT asynchronous, then you should just be able to do this:
    var txt = tbfunction_pgExecuteRemote("main", "tbfunction_pgTBObjSet", ["sharedActions", "getText", code]);
    if (typeof txt != 'undefined' && txt != '-') {
        //txt is valid ... do some stuff
    } else alert("A problem occurred in retrieving the data.");
Asynchronous scripts have to be written to make a request and wait. An example of this is the PowerPac function XMLHttpRequest(). This function can be either asynchronous or it can pause the script until a server returns a reply. It is the developer's choice, however nowadays it is recommended to go asynchronous wherever possible.

Most functions and scripts are NOT asynchronous. That being said, many applications have other timed events going on and event listeners waiting to respond to various user actions, etc. For example, if you have a timer running that is limiting how long a student has before their response is locked, then the timer is asynchronous while it runs, but what is does when the timer expires is NOT asynchronous.

Modern browsers now support promises which enable JavaScript to work more asynchronous. I've played around with promises, but have yet to find a use for them in my work. Promises are not allowed to interact with the DOM so you cannot manipulate objects with a running promise. However, when a promise finishes, you can run functions which manipulate the DOM. I need to educate myself more on this new technology to make it useful.
 
Clifton
Site Admin
 
Posts: 732
Joined: Tue Jan 14, 2014 1:04 am

Re: Remote ExecuteRemote

Postby John Robin Dove » Sun Dec 06, 2020 4:59 am

Thanks for making that clear. My function is NOT asynchronous so the 'waiting loop' is completely unnecessary. I knew that this was the case for the variable setting functions like setting the variable previews but for some reason I convinced myself that getting translations in the database would be asynchronous. I seem to remember my attempts failing and finding a solution involving the waiting loop. I guess they probably failed for some other reason and the success of the loop was just random. Anyway I have progressed considerably as I now understand what I'm trying to do. :) I still find the translation system pretty awsome. I just send a number and it finds the text in the required language amongst 700 lines of text in nano-seconds! Maybe this is why I felt it needed more time to do the job!

About promises. I remember having huge problems with the recording system because of a promise or maybe a broken promise! It was to do with obtaining the user's consent to use the microphone but unfortunately I no longer recall the details. At the time I read this https://pouchdb.com/2015/05/18/we-have-a-problem-with-promises.html but I don't really understand much of it now.
John Robin Dove
 
Posts: 486
Joined: Thu Jan 23, 2014 4:35 am


Return to General Discussion

Who is online

Users browsing this forum: No registered users and 4 guests

cron