Execution of JavaScript code
Purpose of this page
In the previous page, we explained the page loading. In this page, we'll learn how to execute JavaScript code via the Rice.
Table of contents:
Pane and browser
Cooker is a two-pane application. The Rice code on this page is implemented assuming it runs in the left pane, Main-Browser.
You can also do it in the right pane, but the result of the run will appear in the Main-browser.
jsexecutor class
Cooker is software for manipulating web pages, and the manipulation is done by using JavaScript.
The browser class is a class that has been collected features that operate the browser. Similarly, there is a class that has been collected features that manipulate JavaScript. That is the jsexecutor class.
The features of the jsexecutor class.
Executes any JavaScript code directly.
Embeds a new script tag inside a web page.
Calls a JavaScript function that is in a web page.
Passes a string between JavaScript and Rice.
Fires the Notice event from JavaScript.
In the following sections, we will learn how to use the jsexecutor class.
Direct execution via Rice
Let's modify the example.cook on the previous page to explain the direct execution of JavaScript and execute the message display in JavaScript.
The following is the modified example.cook. Lines 5, 18, and 20 are the added and modified.
1: | class main |
2: | browser br; |
3: | switch sw; |
4: | message m; |
5: | jsexecutor jse; // Added field declaration. |
6: | open method void start() |
7: | sw = br.LoadCompleted(this, "LCHandler(dictionary)"); |
8: | sw.Start(); |
9: | br.LoadUrl("https://www.example.com/"); |
10: | endmethod |
11: | |
12: | open method void end() |
13: | sw.Stop(); |
14: | endmethod |
15: | |
16: | open method void LCHandler(dictionary dic) |
17: | if(dic.IsSuccess) |
18: | jse.Execute("alert('Page loading is successful.');"); // Direct execution. Display messages using JavaScript. |
19: | else |
20: | jse.Execute("alert('Page loading is failure.');"); // Direct execution. Display messages using JavaScript. |
21: | endif |
22: | endmethod |
23: | endclass |
The jse that is declared at the line 5 is field of the jsexecutor class.
Lines 18 and 20 call the Execute(string) method of the jsexecutor class via jse.
The Execute(string) method executes the argument as JavaScript code.
You can use variables of the "jsexecutor" class without definition. In that case, they represent jsexecutor of the Main-browser.
If you run the above code, JavaScript dialog will show after page loading is successful.
The variations of the Execute method are shown below.
1: | Execute(string) | Executes the argument as JavaScript code. It will be IIFE. |
2: | Execute(string,bool) | Executes the argument as JavaScript code. You can select whether it will be IIFE. |
3: | ExecuteFromFile(string) | Loads JavaScript code from the file and executes it. It will be IIFE. |
4: | ExecuteFromFile(string,bool) | Loads JavaScript code from the file and executes it. You can select whether it will be IIFE. |
Immediately Invoked Function Expression (IIFE)
1 and 3 convert forcibly the argument into an IIFE.
When the code, jse.Execute("alert ('page load successful.');");, is executed, the code passed to the JavScript environment is as follows.
We will omit the explanation of JavaScript-related terms and functions such as the immediately Invoked Function Expression (IIFE) and alert() function.
1: | (function(token){ |
2: | alert('page load successful.'); |
3: | }("0f8fbd59d9cb769fa16570867728950e")); |
The first and third lines are added to the argument and converted into IIFE.
The token of the argument on the first line and the value of the token on the third line will be described later.
Actually, IIFE don't need in the above example since it is just calling the alert() function. But in general, executing JavaScript code as IIFE has two advantages.
The first advantage is to prevent namespace pollution.
If you use the var statement in a function definition, the variable has function scope. The reference range of the function scope is limited to the inside of the function. And, the variable is destroyed at the end of the function.
In other words, it has the least impact on other JavaScript code.
If you use a var statement outside the function definition, the variable has global scope. Global scope variables - global variables - remain for the life of the page.
Moreover, if a global variable with the same name already exists, it will be overwritten. If that happens, the page may not work properly.
IIFE is function definition. Therefore, it can prevent namespace pollution.
The second advantage is that you can receive the return value from JavaScript.
Rice can receive values as a result of JavaScript function calls.
If a JavaScript function returns a value in a return statement, you can receive it in Rice. A return statement that is not in the function definition will ocuurr a syntax error. Such JavaScript code cannot return a value.
Therefore, you need to put the return statement in the function definition to receive the value from JavaScript.
IIFE are function definitions that is executed immediately. So, even if the argument of the Execute(string) method contains a return statement, it works as expected.
You may use other version of Execute method if IIFE get in the way.
token
As mentioned above, the argument "token" is set at the time of the conversion. It is a string represents 32-digit number.
Cooker's JavaScript extensions require this token. For example, Cooker's JavaScript supports output to a local file, but to use it,
1: | // Write "output data" to filename.txt. |
2: | window.cooker.storage.Write(token, "filename.txt", "output data", true); |
Do it like this. window.cooker.storage.Write() will not work unless you give the correct token.
The value of token is given at the time of the conversion. This allows you to limit the use of extensions to code derived from user.
In ver 3.0.2, Token getter is added to jsexecutor class. This getter returns the token value above. If you want to write Javascript code that uses extensions without conversion by the system, you can use this getter.
Another way of the IIFE
You can convert by writing the instruction directly in the JavaScript code. This is the highest priority instruction.
1: | // Named comment |
2: | /**JS** |
3: | ///immed |
4: | alert(token); |
5: | */ |
6: | |
7: | /**JSASYNC** |
8: | ///async |
9: | alert(token); |
10: | */ |
11: | |
12: | class main |
13: | rice rr; |
14: | jsexecutor jse; |
15: | open method void start() |
16: | jse.Execute(rr.JS, false); // Even if the Execute() method that doesn't convert, the JavaScript code will be converted. |
17: | endmethod |
18: | endclass |
Execute(rr.JS, false) on the 16th line does not convert the code, but the instruction within JavaScript is prioritized and convert to IIFE.
"///immed" on the 3rd line and "///async" on the 8th line are instructions of conversion. If the beginning of the JavaScript code is these strings, conversion will be performed.
Conversion by "///immed" is as follows. The 1st and 4th lines are added.
1: | (function(token){ |
2: | ///immed |
3: | alert(token); |
4: | }("0f8fbd59d9cb769fa16570867728950e")); |
Conversion by "///async" is as follows. The 1st and 4th lines are added.
1: | (async function(token){ |
2: | ///async |
3: | alert(token); |
4: | }("0f8fbd59d9cb769fa16570867728950e")); |
Return value from JavaScript
You can return the value to Rice by using return statement and throw statement from JavaScript.
Only numbers, booleans, and strings can be returned. In addition, these are returned as string.
If a number is returned, a string that can be parsed into a number is returned. (e.g.: 1, 20000, 256.35)
If a boolean value is returned, either true or false is returned as a string.
If a string is returned, the string will be enclosed in double quotes. (e.g.: "returned string.")
If an object, undefined, or null is returned, the return value cannot be parsed into the above type, so null as string is returned instead. In the case of JavaScript execution fails or does not return a value are also the same.
The above rules apply to all JavaScript return values.
script tag
In the above example, we executed the JavaScript code by giving directly, but you can also embed the code to execute in the page.
There are three methods for embedding code.
1: | SetJS(string,string) | Creates a script tag and embeds JavaScript codes. |
2: | SetJSFromFile(string,string) | Creates a script tag and embeds JavaScript codes. The codes load from a file. |
3: | SetSRC(string,string,string) | Creates a script tag and sets the src attribute. |
Details of these methods are different, but basic behavior is the same.
Creates a new script tag and add it to the end of the document's body tag.
We will omit the explanation of HTML-related terms and functions such as script tag and body tag.
The followings are examples of the script tag that will be created.
JavaScript code embedding:
jse.SetJS("function examplefunc(mess){alert(mess);}","exid");
The following script tag is created and added to the end of the body tag.
1: | <script id="exid"> |
2: | function examplefunc(mess){alert(mess);} |
3: | </script> |
src attribute:
Loads the jQuery as an example.
jse.SetSRC("https://code.jquery.com/jquery-3.5.1.min.js", "exid", "utf-8");
The following script tag is created and added to the end of the body tag.
1: | <script src="https://code.jquery.com/jquery-3.5.1.min.js" id="exid" charset="utf-8"> |
2: | </script> |
The SetSRC() method executes asynchronously.
Loaded code may not exist immediately since loading from an external site is time consuming.
Let's add the call of the SetJS() method to the sample code.
1: | class main |
2: | browser br; |
3: | switch sw; |
4: | message m; |
5: | jsexecutor jse; |
6: | open method void start() |
7: | sw = br.LoadCompleted(this, "LCHandler(dictionary)"); |
8: | sw.Start(); |
9: | br.LoadUrl("https://www.example.com/"); |
10: | endmethod |
11: | |
12: | open method void end() |
13: | sw.Stop(); |
14: | endmethod |
15: | |
16: | open method void LCHandler(dictionary dic) |
17: | if(dic.IsSuccess) |
18: | jse.SetJS("function examplefunc(mess){alert(mess);}","exid"); // Embeds. |
19: | jse.examplefunc("Page loading, code embedding, and direct call are successful."); // Direct call. |
20: | else |
21: | jse.Execute("alert('Page loading is failure.');"); |
22: | endif |
23: | endmethod |
24: | endclass |
Direct call
You can call JavaScript functions in the page from Rice.
There are a couple ways to call it, but here we will explain how to call a JavaScript function directly, that is, how to call a JavaScript function using Rice's method call notation.
Please take notice of lines 18 and 19 of the example.cook above.
It is embedding the JavaScript function - examplefunc(mess) - using the SetJS() method and it is calling it directly.
19: | jse.examplefunc("Page loading, code embedding, and direct call are successful."); |
It is calling a JavaScript function from jse, but the call looks the same as a member method call.
The jsexecutor class does not have the examplefunc(mess) method as a member, but this call succeeds. This is because there is a JavaScript function with the same name on the web page.
The jsexecutor class converts non-existent member method calls into JavaScript function calls.
If the corresponding JavaScript function does not exist, JavaScript raises an exception and returns control to Rice.
An exception in JavaScript is not notified to Rice. If an exception occurred in JavaScript, returns the string null to Rice and exits immediately.
Rice receives the string null even if JavaScript exits with "return null ;". It receives the string null even if there is no return value.
Returned values are the same. That is, these situations are indistinguishable. This fact means that Rice cannot detect an exception that occurs in JavaScript.
If an exception in JavaScript is important, you should use a try statement to convert the exception to an appropriate return value.
Arguments that can be passed to JavaScript functions by direct call are limited to one of the int, long, real, bool, and string classes.
There is no limit to the number of arguments. Pass the arguments in the order and number required by the JavaScript function. More arguments than required are ignored.
The examplefunc(mess) function does not return a value, but you can also receive a return value from a JavaScript function. As already explained, the return value is returned as a string.
If the method name of the jsexecutor class and the JavaScript function name are the same, the method call of the jsexecutor class takes precedence.
In such case, Please use the CallJSFunction (string, ...) method.
String transfer
It would be nice to be able to easily move large amounts of data between Rice and JavaScript.
Data can be moved via a function call. However, function calls are not meant to move data and are not suitable for moving large amounts of data.
Rice and JavaScript are different environment, so complex data like JavaScript object cannot be moved directly.
The data you can move is limited to basic types like the int, long, real, bool, and string classes.
To move a large amount of data under this limitation, it would be realistic to serialize the data into a string and pass it.
Cooker has the ability to pass strings between Rice and JavaScript.
Rice : jsexecutor class
1: | GetData() | Receives a string set in JavaScript. |
2: | SetData(string) | Pass a string to JavaScript. |
JavaScript : window.cooker.external object
1: | GetData(token) | Receives a string set in Rice. It is an asynchronous function. |
2: | SetData(token,object) | Pass a string to Rice. It is an asynchronous function. |
JavaScript : window.cooker.data object
The argument of the jsexecutor's SetData(string) method will be set to this object.
If we use the jse variable of jsexecutor class, the data transfer is as follows.
Receives in Rice:
1: | string dataFromJs = jse.GetData(); |
Send from Rice:
1: | jse.SetData("string data to JavaScript."); |
Receives in JavaScript:
1: | let dataFromRice = await window.cooker.external.GetData(token); |
Or | |
2: | let dataFromRice = window.cooker.data; |
The window.cooker.external.GetData(token) is an asynchronous function. You need to use "await" to receive the string. "await" can only be used within the definition of an async function. You need to use asynchronous IIFE with "///async".
Send from JavaScript:
1: | window.cooker.external.SetData(token,"string data to Rice."); |
By using of these, data can be transferred between Rice and JavaScript.
See the links for the behavior of individual methods and functions.
Notice event
If JavaScript can invoke Rice code directly, It is nice. However, there is no way to call the Rice code directly from JavaScript.
You cannot invoke Rice code directly, but you can delegate to invoke it.
JavaScript : window.cooker.external object
1: | Notice(token,object) | Fires the Notice event of the jsexecutor class. It is an asynchronous function. |
For example.
1: | window.cooker.external.Notice(token,'A string data which will be passed to the Rice.'); |
This function fires the Notice event of the jsexecutor class. If a handler is set for the Notice event, the argument of the Notice function is passed as a string to the argument of the event handler.
If you judge this argument, you can invoke the appropriate Rice code.