Anonymous functions in Siebel

I was on a Project, that was performing an upgrade from Siebel 7 to Siebel 8, and discovered  parts of their Application was implemented using undocumented eScript features. The code was written by graduate web developers, who used their trade to write ECMA compliant code, but unfortunately some of these features were unsupported by Siebel 8.

One such feature was the usage of anonymous functions, which has the following benefits.

* Provides locally scoped variables
* Can be used as a call back function
* Allows better code readability
* Avoids extra named reference on the current scope

Unfortunately anonymous functions, or even function expressions cause a reference error in Siebel 8.


> ReferenceError:':-$753' is not defined

Siebel customers who need a viable substitute have the option of using the Function constructor, which is appropriately documented by Oracle here.
http://docs.oracle.com/cd/B40099_02/books/eScript/eScript_JSReference189.html

Let’s look at a real world scenario to see where this can come in handy.

During development, a Siebel developer might come across the need to enumerate a PropertySet. The following code shows a basic implementation of the enumerator function.


var propName = oPS.GetFirstProperty(), 
propValue ="";
while (propName != "")  {
                propValue = oPS.GetProperty(propName);

                //###  Do something with propName and propValue ###

                propName = oPS.GetNextProperty();
}

I propose that we create a utility for Siebel developers to easily loop over the properties of a single PropertySet level, without the above verbosity, using the following syntax.

1. [<PropertySet> ].psEach( <Callback> )
or
2. util.psEach( <PropertySet>, <Callback> )

The implementation of option 1 is outside the scope of this article, but it can be easily built by customers who already have a familiar construct in their Library.

However option 2 should be a little more familiar to customers who use script libraries in Siebel. util is the name space, or the local reference to a name space from an eScript library. psEach is the logical method that performs the enumeration.

The following diagram shows, how we should use the Function object in this context.


1.       ‘Class_Util_PSNodeEach’ is the literal name for util.psEach.
2.       The PropertySet that we wish to enumerate
3.       A function object that will receive the Name, Value pair.
4.       This function will receive two inputs n, v. This is optional, as the arguments object can be used just as easily.

The following screenshot of The Harness shows the definition of ‘Class_Util_PSNodeEach’, the code that was used to set up the test case, and the expected results.


Line 15. above shows where the magic happens, this API essentially replaces 9 lines 
of code (x the number of times this construct is used in your Application) with 1 line.

Just a friendly reminder that this code has been simplified for illustration purposes, it is not is not production ready or is it 
optimized to be used in the real world.

> new Function("n,v", "log.info(n+' = ' +v " );

The syntax above creates a new function on the fly, which expects an input of "n" and "v". It is invoked by line 5 in the screenshot above, and prints it out using a library method that outputs the result back to the UI. 


This allows us to hide the clunky looping construct that is normally required to enumerate a PropertySet in eScript, and expose a much better API through psEach.

Are there any side effects?

* Code written inside the Function constructor is supplied as a string, which has to be properly escaped

* The function constructor will run in the scope of its instantiated instance. This is normally the current Siebel event handler or Business Service, or the code library where psEach lives, but it could also be the instance of any object created by the new Operator. What this all means is that, the callback function won’t inherit the local scope of your object, and will not have access to your local function’s variables unless they are passed in

* These functions won’t appear in your Siebel eScript object explorer. This could also be an advantage.

In context of the problem that we are trying to solve, the above list doesn't apply, but it should still be taken into consideration if you want to utilize this elsewhere in your Application, and please check with your local eScript architect if you are unsure of the impacts.

Are there any speed difference with either option? Once the function is parsed, it will perform just as well as the function declaration. The following test case shows that with our set up above, there are no discernible speed advantage of using either method.



The Function constructor is possibly one of the most underused features of eScript, but when used in the right way, it can provide your project with the capability to organize, and reduce the amount of code that your developers have to write, test, and maintain. 

Further Reading Topics

Function Constructor JavaScript
Immediately Invoked Function Expressions (IIFE) or
Self Executing Anonymous Functions (SEAF).




Reactions:

8 comments:

  1. Hi, Jason!
    Thanks for sharing this valuable information. Could you post an example of anonymous function call that caused problems in version 8? Just wondering because following piece of code works well in 8.1.1.5:

    (function() {
    TheApplication().RaiseErrorText("Alert!!!");
    })();

    ReplyDelete
  2. Hi Yaroslav

    Thank you for verifying that. I can confirm that your basic test case above does indeed work.

    However, it fails when it is used inside an instantiated class object. The following code can be used to recreate this particular scenario.

    function Service_PreInvokeMethod (MethodName, Inputs, Outputs)
    {
    var iRet = ContinueOperation;
    switch(MethodName){
    case "test":
    iRet = CancelOperation;
    var a = new myClass();
    a.init(Outputs);
    break;
    }
    return iRet;
    }
    function myClass()
    {
    myClass.prototype.init = init;
    }
    function init(Outputs)
    {
    try{
    (function(Outputs){
    Outputs.SetProperty("Anon fn", "THIS FAILS" );
    })(Outputs);
    }catch(e){
    Outputs.SetProperty("ERROR > Anon fn", e.toString() );
    }
    try{
    (new Function('Outputs','Outputs.SetProperty("Fn contructor", "THIS WORKS");'))(Outputs);
    }catch(e){
    Outputs.SetProperty("ERROR > Fn contructor", e.toString() );
    }
    }

    This is the result.

    >Fn contructor = 'THIS WORKS'
    >ERROR > Anon fn = 'ReferenceError:'init:-$10' is not defined
    Note: If the logic is moved outside the compiled named function, then it will work.

    This kind of code structure will impact implementations of eScript libraries which construct its APIs in this fashion.

    ReplyDelete
  3. Hi, Jason!
    I've played a little with your code and there is a thing that seems weird to me:

    function Service_PreInvokeMethod (MethodName, Inputs, Outputs)
    {
    var iRet = ContinueOperation;
    var myClass = {
    init : function(Outputs){
    (function(Outputs){
    Outputs.SetProperty("Anon fn in object direct call", "THIS WORKS" );
    })(Outputs);
    }
    }
    switch(MethodName)
    {
    case "test":
    iRet = CancelOperation;
    var a = myClass.init;
    a(Outputs);
    try
    {
    myClass.init(Outputs);
    }
    catch(e)
    {
    Outputs.SetProperty("ERROR > Anon fn in object", e.toString());
    }
    break;
    }
    return iRet;
    }

    Outputs:
    Anon fn in object direct call = THIS WORKS
    ERROR > Anon fn in object = ReferenceError:"Service_PreInvokeMethod:-$329:-$328" is not defined Object.Service_PreInvokeMethod:-$329 line: 33 Service.Service_PreInvokeMethod line: 57

    So, if we reference internal function directly it works normally.
    Concerning the change in 8.x version, I presume that it is because ST engine became the default one. Is there any chance to test the code on 7.x with ST engine enabled? Unfortunately I don't have any server with 7.x on hand.

    ReplyDelete
  4. Hi Yaroslav

    Thanks for trying that out. I've also noticed the same behavior.

    ReplyDelete
  5. Tested the above code on sample database with ST engine disabled and it works fine. So, it is definitely ST engine's bug/feature.

    ReplyDelete
  6. Can anyone please tell me that how to write a utility that will read the business service of siebel tool and ll give output as variables which was not nullified??

    ReplyDelete
  7. Hi Alok,

    This may be what you're after
    http://siebelunleashed.com/siebel-open-variable-analyzer/

    ReplyDelete

Comments are open to all, please make it constructive.