Rapid Browser Script Development

This article was written pre Open UI and was locked up in vault, because Browser Script was becomming old school. But as an early user of Open UI, I've found the need to rapidly modify and troubleshoot browser script issues, and remembered the same technique which is used in HI, is also applicable to Open UI.

This article might be more appropriate for a Browser Script reunion party, but it has been released for those who may still struggle with Browser Script, and for Open UI customers who may still rely on legacy Browser Script.

-----------------------------------------------------------------------------------

I asked a trusty colleague to put a line of browser script behind a certain applet and perform an incremental compile on the server for me.

Colleague: What browser script command do I need to put on this applet?
Me: Anything you want, make it display an alert for me.
Colleague: Where should I put it? Applet Load? PreInvoke?
Me: It doesn't matter, just make sure it compiles.

10 minutes later, the server was rebooted, I got my new browser scripted applet and my colleague was free to continue his own work.

Unfortunately, when I took a look at the browser script behind the applet, my colleague inadvertently put a typo in the code, which caused the following error in the IEDTB console



Fortunately this doesn't matter for what we're about to do. In fact, the browser script didn't have to be valid, it can be total junk, but as long as it compiles and produces the browser script files, we're in business. What this does is forces Siebel to produce the necessary "hooks" from the UI to the external JS files.

First we're going to fix that typo. Goto the web server, and find the generated browser script JS file for that Applet.

You'll find that it will look something like this.



We can fix that typo by re-writing the browser script error on the fly and instead of displaying a dialog box on Applet PreInvoke, we will re-implement the code to use a less obtrusive console.log on Applet Load, which means we will have to introduce a new OnLoad handler.

Change your JS file to the following.

function Web_Browser_Entry_Applet_PreInvokeMethod(name, inputPropSet){
 return ("ContinueOperation");
}

//Artificial Onload method hanlder
function Web_Browser_Entry_Applet_Load(applet){
 //write to the IEDTB console
 console.info("I Love Disco");
}

function Web_Browser_Entry_Applet(applet){
 if(applet != null){
  this._applet = applet;
  applet.shado = this;
 }
 
}
new Web_Browser_Entry_Applet(null);
Web_Browser_Entry_Applet.prototype=new top.JSSAppletShadow();
Web_Browser_Entry_Applet.prototype.OnPreInvoke=Web_Browser_Entry_Applet_PreInvokeMethod;
//Attach a new handler for Onload
Web_Browser_Entry_Applet.prototype.OnLoad=Web_Browser_Entry_Applet_Load;
theApplication=top.theApplication;
TheApplication=top.theApplication;


Open the IE Developer Toolbar by pressing F12, clear your browser cache, disable script debugging, and reload the Screen. Follow those steps exactly and your new change will take effect immediately.

Your screen should look like this.



Next let’s give ourselves direct access to the Applet scope from the Browser, this will allow us to write Browser script on the fly in context of the Applet, without reloading the browser!

Change your browser script file to include the following

//recreate the function on the global scope
top.OnPreInvoke=function(name, inputPropSet){
 console.info(name);
 return ("ContinueOperation");
}

//recreate the function on the global scope
top.OnLoad=function(applet){
 //create a global reference to the current applet
 top.thisApplet=this;
 console.info("I Love Disco");
}

function Web_Browser_Entry_Applet(applet){
 if(applet != null){
  this._applet = applet;
  applet.shado = this;
 }
 
}
new Web_Browser_Entry_Applet(null);
Web_Browser_Entry_Applet.prototype=new top.JSSAppletShadow();
//repoint the prototype functions to our new globals
Web_Browser_Entry_Applet.prototype.OnPreInvoke=top.OnPreInvoke;
Web_Browser_Entry_Applet.prototype.OnLoad=top.OnLoad;
theApplication=top.theApplication;
TheApplication=top.theApplication;


There are two key pieces in play here.

1) top.OnPreInvoke;

This line shifts execution to a global function that we’ve attached to the main window. Browser Script files are loaded from a hidden frame, so it must be attached to the top, for the console to access.

This global reference allows us to modify the definition of the function, and run new code without reloading the browser.

2) top.thisAppet

This is similar to the above, but this line provides us with access to the Applet instance.

Click Query, and Cancel, to see the results of this dynamic browser script.





We now have direct access to the runtime context of the Applet, and we can develop directly from the IE console.

Overwrite the PreInvoke Handler, by pasting the following code into the console. Click “Query”, and we now see a dialog box.

top.OnPreInvoke=function(name, inputPropSet){
    console.info(name);
    switch(name){
       case "NewQuery":
          alert(name);
          break;
    }
}


Type the following code into the console, and what happens to the Description field

top.thisApplet.FindControl("Description").SetValue("I Love Disco");




We have just learned how to rewrite Browser Script on the fly, and handle events that are not defined in the repository.

The last step in this Rapid Browser Script development process, is to make sure that when you are happy with your browser script, remove those global references, copy the logic back into Tools, in the correct event handler and compile it to validate your handy work.

-----------------------------------------------------------------------------------

Open UI: Property Set Methods (GetChildByType)

Open UI: New Property Set Structure
Open UI: Property Set Methods (Copy, DeepCopy, Clone)
Open UI: Property Set Methods (Fixing Copy)
Open UI: Property Set Methods (Why Clone?)

The new chapter in bookshelf on Open UI Property Sets has a new method called GetChildByType. This has to be the most interesting method that we’ve seen so far. I’ve seen numerous custom implementations of this handy method written in eScript, and it’s about time Siebel have provided it out of the box, but sadly its only available on the Browser.

To understand how this can be useful, the follow is an example of code that was written to loop through a SiebelMessage n times to look for a child Property Set with a certain type.



You need to have eyeballs of steel to follow this example, but this should be a thing of the past. With this new method, we should be able to reduce the above example to 1 line.

Lets test it out.

//setup test case
//construct a deep hierarchy
var ps=theApplication().NewPropertySet();
var psChild1=theApplication().NewPropertySet();
var psGrandChild1=theApplication().NewPropertySet();
var psGrandChild2=theApplication().NewPropertySet();
var psGrandChild3=theApplication().NewPropertySet();
var psGreatGrandChild1=theApplication().NewPropertySet();
var psChild2=theApplication().NewPropertySet();
ps.SetType("Parent");
ps.SetValue("ParentValue");
ps.SetProperty("Mum","Georgette");
ps.SetProperty("Dad","George");
//first level child
    psChild1.SetType("Child 1");
    psChild1.SetProperty("Name","Bob");
    psChild1.SetProperty("Gender","Male");
        psGrandChild1.SetType("Grand Child 1");
        psGrandChild1.SetProperty("Name","Bobby's Kid 1");
        psGrandChild1.SetProperty("Gender","Male");
            psGreatGrandChild1.SetType("Great Grand Child 1");
            psGreatGrandChild1.SetProperty("Name","Bobby's Grand Kid 1");
            psGreatGrandChild1.SetProperty("Gender","Male");
        psGrandChild2.SetType("Grand Child 2");
        psGrandChild2.SetProperty("Name","Bobby's Kid 2");
        psGrandChild2.SetProperty("Gender","Male"); 
//first level child     
    psChild2.SetType("Child 2");
    psChild2.SetProperty("Name","Jane");
    psChild2.SetProperty("Gender","Female");
        psGrandChild3.SetType("Grand Child 3");
        psGrandChild3.SetProperty("Name","Janes's Kid 2");
        psGrandChild3.SetProperty("Gender","Male");       
psGrandChild1.AddChild(psGreatGrandChild1);
psChild1.AddChild(psGrandChild1);
psChild1.AddChild(psGrandChild2);
psChild2.AddChild(psGrandChild3);
ps.AddChild(psChild1);
ps.AddChild(psChild2);

 
console.log("Test case 1: " + ps.GetChildByType("Child 1").GetType() );
console.log("Test case 2: " + ps.GetChildByType("Child 2").GetType() );
console.log("Test case 3: " + ps.GetChildByType("Grand Child 1") );
console.log("Test case 4: " + ps.GetChildByType("Grand Child 2") );





Disapointingly, the results above show that “GetChildByType” only returns child Property Sets directly attached to the input Property Set, in other words, it doesn’t perform a recursive search of the entire Property Set as we might hope.

Custom GetChildByType

This falls back onto the customer to implement a custom GetChildByType that actually searches beyond the first level.

The safest place to house this new Method, is in a custom client side library.

There are multiple approaches on how to build this client side library, but for this article, we will stick with a simple example using the same style of registering a custom PR/PM.



For educational purposes, the following code shows how this function can be built, please don’t use the code without understanding what it does.



Next ensure that you load your custom logic in the custom_manifest.xml



Heres how one would use this new library function.
var psGrandChild=SiebelApp.ABC.GetChildByType(ps,"Grand Child 2");
//returns Property Set


Conclusion

GetChildByType is just one of the many new methods exposed in the Open UI Property Set. Although the intentions were well made, the limitations of the current implementation, make it hard for customers to embrace. Until Siebel provides a method in future to perform a recursive search, customers will do well to augment Siebel's API, with their own client side libraries.

Open UI: Property Set Methods (Why Clone?)

Open UI: New Property Set Structure
Open UI: Property Set Methods (Copy, DeepCopy, Clone)
Open UI: Property Set Methods (Fixing Copy)


Previously on Open UI: Property Set - Methods (Copy, DeepCopy, Clone), we discovered two new ways to copy a Property Set (DeepCopy and Clone).

DeepCopy is similar to Copy, except it merges two Property Sets together, while Clone performs a full copy of a PropertySet just like Copy, which raises the question around the meaning of life for Clone...until now.

To understand why Clone exists, we need to go back to the basics of ECMA script, which eScript, Browser Script and Java Script are all based upon.

Argument Passing

It is common knowledge, that there are two kinds of variables.

Primitive:
Number, Boolean, String, Undefined, Null

Complex/Composite:
Object, Array, Property Set, BusComp, Business Service, BusObject


Property Sets, BusComps, BusObject, Business Services are specific to the Siebel environment, and are considered to be complex/composite Objects, because they are derived from the base Object.


It is also "common" knowledge that complex/composite objects are passed into functions by reference. This view is supported by Bookshelf.

Passing Variables to Functions in Siebel eScript

Composite types such as objects and arrays are passed by reference.

However this is a common misconception, in fact all function arguments are actually passed in by value. Siebel professionals who work with Siebel Inputs/Outputs day in and day out, will testify to the opposite.

This is not the first time we've challenged the view of the world, so let me back this up with evidence, and what better way than to do it than with Siebel's Property Set Copy and Clone?


//Create new Property Set
var ps = SiebelApp.S_App.NewPropertySet();
//Set Type on original PS
ps.SetType("A");
console.log( ps.GetType() );
//Type stores "A"

//Perform Copy on ps
copyPS(ps);

function copyPS(ps){
     var ps2 = SiebelApp.S_App.NewPropertySet();
     //Create a new PS and set the type
     ps2.SetType("B");
     //perform a copy on ps2
     ps.Copy(ps2);
}

console.log( ps.GetType() );
//Type now stores "B"



The above test case starts off with a Property Set with a Type "A", which is passed into copyPS, which modifies the original Property Set's Type to "B". This is what we know and expect.

Watch what happens when we use Clone.

//Create new Property Set
var ps = SiebelApp.S_App.NewPropertySet();

//Set type on Original PS
ps.SetType("A");
console.log( ps.GetType() );
//Type stores "A"

//Perform Clone on ps
clonePS(ps);

function clonePS(ps){
     var ps2 = SiebelApp.S_App.NewPropertySet();
     //Create a new PS and Set the type to a different value
     ps2.SetType("B");
     //If this argument was passed in by Reference
     //it should contain the new type above.
     ps=ps2.Clone();
}

console.log( ps.GetType() );
//Type still stores "A", 
//the cloned value was lost!



With the same expectation as above, if ps were passed in as a reference, we would see that the original Property Set's type would be modified to "B", instead the original Property Set remains’ unchanged.

Here is another derivative of the above example

var ps = theApplication().NewPropertySet();
ps.SetType("A");
var ps2 = theApplication().NewPropertySet();
ps2.SetType("B");

//Perform Clone on ps
clonePS(ps, ps2);

function clonePS(ps, ps2){
     //If this argument was passed in by Reference
     //it should contain the new type above.
     ps=ps2.Clone();
}

console.log( ps.GetType() );
//Type still stores "A", 
//the cloned value was lost!



In the second example, I’ve moved both ps and ps2 declarations to global scope, and passed in the both Property Set "references". The function clonePS tries to reassign the reference but fails.



To explain this behaviour, think of ps as a reference that is being passed in by value and not as a reference passed in by reference. The reference can be used to modify the original Object, but the reference to the original object itself cannot be altered inside the function. The local copy can be changed to point to another object, however this local copy is independent from our original object.

eScript developers will be wondering, will this really apply to them? Lets fire up The Harness, and create a new test case using Server Script.



The above test case shows ps1 and ps2 being passed into a function, where the ps1 reference is set, and then reassigned to another Property Set. From our understanding above, the following statement fails

ps1=ps2

There are no surprises at this point, as the results reflect what we saw in the browser.



We can conclude that this behaviour exists in eScript/BScript and JavaScript, which are all flavours of the ECMA specification. The only exception to this rule is that with eScript, we get a special function argument modifier "&" that allows us to pass a reference by reference.

Conclusion

Siebel designed Clone to behave fundamentally different from Copy and DeepCopy, and provides it as a distinct alternative for those circumstances that the original Property Set should be preserved.

On the surface Copy, DeepCopy, and Clone look like they behave the same, but its only when we look beneath the surface, that we appreciate the subtleties of Siebel Property Sets. It brings me back to my favourite Yum cha restaurant, and being presented with Prawn & Basil dumpling, Deep fried Prawn Dumpling, and Prawn Gyoza Dumpling.

All claim to be Prawn dumplings, they have different presentations, but it’s not until you dig into it, that you begin to enjoy the subtleties of Prawn that is deep friend, pan fried, and steamed. The next time you copy that Property Set, spare a thought for the Siebel engineers who provided the different flavours of Copy, DeepCopy, and Clone, for you to enjoy.

Open UI: Mouse over Tooltips - Part 2

Open UI: Mouseover Tool Tips - Part 1

This is a continuation of Open UI: Mouse Over Tooltips - Part 1, where we invented our own Pre_ShowUI, and Post_ShowUI handlers. These handlers allow us to hook onto the physical renders base class ShowUI method, to trigger global behaviour each time a UI object is rendered.

Let’s imagine a solution where an administrator can log into the Siebel UI, identify an Applet, a Control or List column and specify some text. This will render a fancy mouse over tooltip over the control/list column, the next time the page is reloaded. No scripting, or configuration skills is required!.

Sequence of events

1. The user navigates to a view
2. The page will load all the base applets in the view
3. Post_ShowUI will fire for each applet that is rendered
4. Read Values from Data source
5. Attach tooltip to configured control

Data source

The data source can be as simple as a global JS configuration file, or in our case it will be retrieved from a table on the server. For the sake of simplicity our data source for this article will be the LOV table, with the following mappings.

Description = Applet Name
LIC = Control Name
Value = Tooltip message


The LOV table is unsuitable for a number of obvious reasons, and it is used here only to illustrate the build for this Tool tip configuration. I’ve created some sample records, and filled some values, that we will use a bit later.



Tooltip Loader

The Tooltip loader is fired from our custom global Post_ShowUI event, and performs the following functions.

1. Identify the current applet in context
2. Query the data source for the tooltip definitions
3. Attach tooltips to the Applet Controls/List columns

Reading the Data source

This job should be performed by a server side Business Service in tools, and for convenience I’ve used The Harness to quickly build and unit test the POC for this article.



The results pane shows the output structure that we want to send to the Browser, and confirms that our data is correctly populated in the Property Set.

There are a few custom commands in this example, which will be foreign to most Siebel customers.

util.getLOVObjs
getLOVObjs is a eScript library function that queries the LOV table and returns an Array

[].ObjToStr

ObjToStr is a eScript library function that prints out any Object for debugging

[].PSToStr

Projects with the ABS framework will recognise this utility. This prints out a Property Set for debugging


Script libraries are described here in Bookshelf. The implementation of the Script library is out of scope for this article, but the key take away from this example is, projects should be implementing utility libraries to encapsulate common functionality, and maximise reuse of script in the application.

Tooltip loader logic

The high level logic looks like this

1. Call retrieve Tooltips using a server side Business Service
2. Get Controls map from the current applet
3. Loop through Tooltip configuration
4. Build Control/List Column jQuery Selector
5. Attach Tooltip to Control/List Column


SiebelApp.ABCToolTips.LoadAppletToolTips=function(pm){ 
      try{
            var sAppletId=pm().Get("GetFullId"),sApplet=pm().GetProxy().GetName(),sModId="JLETooltips";        
            
            //Remove all tooltip indicators    
            $("#"+sAppletId+" img["+sModId+"]").remove();

            //look for table prefix, and also determine applet type
            var oAppletProp=SiebelApp.ABCToolTips._GetAppletProp(sAppletId);   
           
            //Get Tooltip definition from server
            var oTooltipCfg=SiebelApp.ABCToolTips._GetToolTipsFrSvr(sApplet);
            if(oTooltipCfg.length>0){

                  // retrieve all controls on current applet
                  var oControlsMap=pm().Get("GetControls");
                  for(var sTooltipCntr in oTooltipCfg){
                        var oOpt=(oAppletProp.isListApplet)?SiebelApp.ABCToolTips.OptionsHeading:SiebelApp.ABCToolTips.Options;            
                        oOpt.content.text = oTooltipCfg[sTooltipCntr]["Msg"];  
             
                        //Lookup control definition
                        var oControl=oControlsMap[sTooltipCntr];
                        if(typeof oControl!=="undefined"){         
                              var sIdSelector=SiebelApp.ABCToolTips._GetControlListColSelector(oAppletProp,oControl.GetName());
                              $(sIdSelector).qtip(oOpt);
                              $(sIdSelector).append("");
                        }else{                 
                        }
                  }          
            }
      }catch(e){
            throw e;
      }
}

SiebelApp.ABCToolTips._GetControlListColSelector=function(oAppletProp,sControlName){
      var sControlNameUnd=sControlName.replace(/\s/g,"_");                   
      var sIdSelector=( oAppletProp.isListApplet )?"#"+oAppletProp.sDataTablePrefix+"_"+sControlNameUnd:"#"+sControlNameUnd+"_Label";
      return sIdSelector;
}  
 
SiebelApp.ABCToolTips._GetAppletProp=function(sAppletId){      
      var oGridId=$("#"+sAppletId+" .ui-jqgrid-view");
      var sDataTablePrefix="";
      if(oGridId.length>0){
            sGridId=oGridId.attr("id").replace("gview_","");

            //column headers are prefixed with 'jqgh_' plus the table id
            sDataTablePrefix="jqgh_"+sGridId;
            var isListApplet=true; 
      }else{
            var isListApplet=false;
      }
      return {"isListApplet":isListApplet,"sDataTablePrefix":sDataTablePrefix}
}

SiebelApp.ABCToolTips.Options={
      content: {     
            prerender : true,
            text: ""
      },
}      

SiebelApp.ABCToolTips.OptionsHeading=SiebelApp.ABCToolTips.Options;



Explanation:
var sAppletId=pm().Get("GetFullId");
var sApplet=pm().GetProxy().GetName();
var sModId="JLETooltips"; 

//Remove all tooltip indicators    
$("#"+sAppletId+" img["+sModId+"]").remove();

//look for table prefix, and also determine applet type
var oAppletProp=SiebelApp.ABCToolTips._GetAppletProp(sAppletId);


The above lines performs some basic initialisation, and determines whether we are dealing with a Form or List Applet

var oTooltipCfg=SiebelApp.ABCToolTips._GetToolTipsFrSvr(sApplet);



This method calls the server, and returns list of child Property Sets, as shown by the results pane of The Harness above.

The actual implementation of this Business Service isn’t provided in this article, because the LOV table isn’t the right Data source for a real solution, and the script library functions that I’ve used are unique to my local environment.

if(oTooltipCfg.length>0){
            // retrieve all controls on current applet
            var oControlsMap=pm().Get("GetControls");

            // Loop around all the Tooltips retrieved from the server for the current applet
            for(var sTooltipCntr in oTooltipCfg){
                        var oOpt=(oAppletProp.isListApplet)?SiebelApp.ABCToolTips.OptionsHeading:SiebelApp.ABCToolTips.Options;     
                        oOpt.content.text = oTooltipCfg[sTooltipCntr]["Msg"];  
            
                        //Lookup control definition
                        var oControl=oControlsMap[sTooltipCntr];
                        if(typeof oControl!=="undefined"){         
                                    var sIdSelector=SiebelApp.ABCToolTips._GetControlListColSelector(oAppletProp,oControl.GetName());
                                    $(sIdSelector).qtip(oOpt);
                                    $(sIdSelector).append("");
                        }
            }          
}



The block of code above is fairly well documented. However, attaching qTip itself requires a bit more explanation.

The syntax for attaching qTip Tooltips looks like this

$(sIdSelector).qtip(oOpt);

The form control selector is basically the Control name with spaces replaced with “_”, appended by “_Label”.

SiebelApp.ABCToolTips._GetAppletProp=function(sAppletId){      
            var oGridId=$("#"+sAppletId+" .ui-jqgrid-view");
            var sDataTablePrefix="";
            if(oGridId.length>0){
                        sGridId=oGridId.attr("id").replace("gview_","");

                        //column headers are prefixed with 'jqgh_' plus the table id
                        sDataTablePrefix="jqgh_"+sGridId;
                        var isListApplet=true; 
            }else{
                        var isListApplet=false;
            }
            return {"isListApplet":isListApplet,"sDataTablePrefix":sDataTablePrefix}
}



List column selectors are a bit harder. It is a combination of a Grid prefix+ the Control name with spaces replaced with “_”. The function above shows the jQuery selector that I used to get the Grid Id.

Finally the oOpt object tells qTip what message to display, how to display the tooltip, and other options such as style.

oOpt.content.text = oTooltipCfg[sTooltipCntr]["Msg"];

In our example above, oOpt.content.text is the property that qTip will display as the message. More details can be found on the qTip website

Trigger

The final piece of this design is to put the following code in your Post_ShowUI handler. This will call the Tooltip loader every time an applet is rendered.



Now enjoy the fruits of our efforts.





Notice, that we have a little feel good icon to indicate to the user which Control/List have a Tooltip.

Conclusion


Tooltips are a lot easier to implement in Open UI, but it doesn’t mean that they are easy to implement. As with anything that is remotely complex in Siebel, there are two approaches one can take.

You can put on a builders hat, and say let’s create individual physical renders, hard code the tooltips into each file, and say job done in 5 minutes. This approach is cheap, but it has a nasty long term effect.

However if we put on our Architect hat, we will need to consider performance impacts, implement a server side script library, build client side libraries, and also satisfy custom requirements in a maintainable fashion. Not all of these issues have been addressed in this Article, but I’ve provided what I promised. An elegant Tooltips implementation in Open UI.

Open UI: Property Set Methods (Fixing Copy)

Open UI: New Property Set Structure
Open UI: Property Set Methods (Copy, DeepCopy, Clone)
Open UI: Property Set Methods (Why Clone?)

In this article, we will look at the potential speed differences between Property Set methods Copy, DeepCopy and Clone. If we started from a clean Property Set, and used Copy, DeepCopy or Clone, to "copy" a Property Set, they would provide the same output and could be used interchangeably.

Just by looking at the function definition for Clone from the previous article, it is leaner, it doesn’t need to perform a Reset, or test for a null input, because Clone is called on an established Property Set, so in theory it will be the fastest. DeepCopy should perform just as fast as Clone for clean Property Sets.

The test case is to compare the run times for Copy, DeepCopy and Clone, using FireFox, IE and Chrome. The aim isn't to compare which browser is faster, but compare the methods relative to each other on the same browser.

I've beefed up our previous test case to include 4 levels in the hierarchy, and run it with the results pictured below.

var ps=theApplication().NewPropertySet();
var psChild1=theApplication().NewPropertySet();
var psGrandChild1=theApplication().NewPropertySet();
var psGrandChild2=theApplication().NewPropertySet();
var psGrandChild3=theApplication().NewPropertySet();
var psGreatGrandChild1=theApplication().NewPropertySet();
var psChild2=theApplication().NewPropertySet();
ps.SetType("Parent");
ps.SetValue("ParentValue");
ps.SetProperty("Mum","Georgette");
ps.SetProperty("Dad","George");
    psChild1.SetType("Child 1");
    psChild1.SetProperty("Name","Bob");
    psChild1.SetProperty("Gender","Male");
        psGrandChild1.SetType("Grand Child 1");
        psGrandChild1.SetProperty("Name","Bobby's Kid 1");
        psGrandChild1.SetProperty("Gender","Male");
            psGreatGrandChild1.SetType("Great Grand Child 1");
            psGreatGrandChild1.SetProperty("Name","Bobby's Grand Kid 1");
            psGreatGrandChild1.SetProperty("Gender","Male");
        psGrandChild2.SetType("Grand Child 2");
        psGrandChild2.SetProperty("Name","Bobby's Kid 2");
        psGrandChild2.SetProperty("Gender","Male");        
    psChild2.SetType("Child 2");
    psChild2.SetProperty("Name","Jane");
    psChild2.SetProperty("Gender","Female");
        psGrandChild3.SetType("Grand Child 3");
        psGrandChild3.SetProperty("Name","Janes's Kid 2");
        psGrandChild3.SetProperty("Gender","Male");         
psGrandChild1.AddChild(psGreatGrandChild1);
psChild1.AddChild(psGrandChild1);
psChild1.AddChild(psGrandChild2);
psChild2.AddChild(psGrandChild3);
ps.AddChild(psChild1);
ps.AddChild(psChild2);
var psCopy=theApplication().NewPropertySet();
psCopy.Copy(ps);

console.log( "---------------------Copy Method---------------------\n"
+util.PSToStr(psCopy) );
var psClone=ps.Clone();
console.log( "---------------------Clone Method---------------------\n"
+util.PSToStr(psClone) );
var psDeepCopy=theApplication().NewPropertySet();
psDeepCopy.DeepCopy(ps);
console.log( "---------------------DeepCopy Method---------------------\n"
+util.PSToStr(psDeepCopy) );


Results:







The results show something extraordinary with Copy, It looks like the recursion traverses the tree, and only the last Property Set of the children of the Parent nodes are returned.

Copy doesn't perform a full copy as we expected. To verify whether this has been broken in prior versions of Siebel, I ran the same test case in Siebel 8.1.1.5 HI, which does correctly copy the full Property Set, confirming the defect in 8.1.1.10.

To see why this is broken, we need to inspect the definition for Copy again.

function JSSPropertySet_Copy(old) {
var i;
var oldChild;
var name;
if (this.axObj != null) {
        if (old == null)
                return (false);
        this.axObj.Copy(old.axObj); // this should return a value
        return (true);
}
this.Reset();
if (old == null)
        return (false);
name = old.GetType();
if (name != null && name != "")
        this.SetType(name);
value = old.GetValue();
if (value != null && value != "")
        this.SetValue(value);
for (name in old.propArray)
        this.SetPropertyStr(name, old.propArray[name]);
for (i = 0; i < old.childArray.length; i++) {
        oldChild = old.childArray[i];
        if (oldChild == null)
                        break;
        newChild = new JSSPropertySet();
        newChild.Copy(oldChild);
        this.AddChild(newChild);
}
return (true);  
}


The key area to focus on would be the for loop, and the recursion line newChild.Copy(oldChild);.

for (i = 0; i < old.childArray.length; i++) {
        oldChild = old.childArray[i];
        if (oldChild == null)
                        break;
        newChild = new JSSPropertySet();
        newChild.Copy(oldChild);
        this.AddChild(newChild);
}       


An interesting thing to note is that the newChild variable is never declared in this function. Although this is generally considered bad practice, as it causes the variable to be created in Global scope, the Clone method uses this same technique, and it does function correctly.

The above block will loop through all the children of the parent, and perform the recursion at each children. This recursion design should work because, the children and all its children are passed into another Copy method, until all the descendants have been found, and returned back the parent.

The problem here is the use of that undeclared newChild variable, and this line newChild = new JSSPropertySet();.

The newChild Property Set is reset each time it iterates through a new child, therefore wiping out the parent Property Sets. The recursion will eventually finish at the last child, which is not necessarily the deepest.

This diagram shows how this flawed recursion will work.



Points 1,2,3,4 shows the path that the recursion will take. The Property Set at points 1,2 will be clobbered by the statement newChild = new JSSPropertySet();. Only Property Sets 3 and 4 will actually make it out alive, as can be seen in the result above.

Fixing Copy

A simple fix is to declare newChild as a local variable as follows.

for (i = 0; i < old.childArray.length; i++) {
        oldChild = old.childArray[i];
        if (oldChild == null)
                        break;
        var newChild = new JSSPropertySet();
        newChild.Copy(oldChild);
        this.AddChild(newChild);
}       


To test our fix, re-running our first test case again, shows this result.



Copy, DeepCopy, Clone times

After putting that fix in, we can now reliably run the performance timings. Again, this is not a browser test, but a comparison of the speed difference between Copy, DeepCopy and Clone. I’ve had to run a lot less iterations in FireFox, and IE, to ensure the execution didn't time out the browser.



Result: In each graph, the baseline is represented by 0, which is the fastest function. The other columns represents, how slow the respective methods are compared to the baseline function.

Summary

* Clone is faster in Chrome and IE, and only losing to DeepCopy by 3% in FF
* There isn’t much different between DeepCopy and Clone in FF and IE
* Copy is undoubtedly slower, being 22% slower than Clone in IE, 8% slower in FF, and 18% slower in Chrome


The bottom line is that Copy is the slowest, the biggest difference will be seen if you have a massive hierarchy to Copy, or if you have to loop several thousand times, but for once off operations, you may see little practical difference. However, I would take any performance benefit where I can get, it if the effort is minuscule.

The fix we put in for this article is purely for educational purposes. I would recommend you use DeepCopy, and perform a Reset on the property beforehand, and wait until this is fixed officially by Siebel in future releases.

The purpose of Clone

Copy has been broken inadvertently, but Siebel would not design a new method with a different name to replace the broken one. Siebel would also not design a better performing function and give it a different name.

The inherent difference in the design of Copy/DeepCopy Vs Clone should provide a little clue to the purpose of Clone, does anyone want to speculate? The answer deserves an in-depth explanation, as it involves a fundamental misconception in Java Script/Browser Script/eScript.

I've tried hard to separate Copy, DeepCopy and Clone, but the mystery seems to get deeper the more we look. In the last article on these 3 methods I'll provide the definitive meaning of life for Clone.

Open UI: Property Set Methods (Copy, DeepCopy, Clone)

Open UI: New Property Set Structure
Open UI: Property Set Methods (Copy, DeepCopy, Clone)
Open UI: Property Set Methods (Fixing Copy)
Open UI: Property Set Methods (Why Clone?)

Let’s begin with the link to Bookshelf on the new Property Set structure in Open UI:

http://docs.oracle.com/cd/E14004_01/books/config_open_ui/appendix_a_API20.html#wp1232047

Underneath this diagram it strangely lists an incomplete list of Property Set methods.

AddChild Method
DeepCopy Method
Clone Method
Copy Method
GetChild Method
GetChildByType Method
InsertChildAt Method
RemoveChild Method
RemoveProperty Method
SetProperty Method


In general the Open UI chapter needs a lot of work in terms of accuracy, but true developers don’t let incomplete documentation get in the way of their learning.

Across this series on the new Open UI Property Set, we will cover some of the most interesting and some of the most confusing methods exposed by the new Property Set API.

We'll start with the most confusing! From the above list, we see 3 seemingly similar methods and their descriptions from bookshelf below (You can tell it was written by an engineer).

DeepCopy Method

The DeepCopy method makes a full copy of the inputPS property set and then parses this copy into the following property set:

this


Clone Method

The Clone method creates a new property set and does a full copy of the following property set:

this


Copy Method

The Copy method copies the following property set:

this




It reminds me of eating Yum Cha, which provides 100 different ways to eat Prawn. In this case, 3 different methods to copy a Property Set. From what I understand of Copy, it also performs a full copy, just like what DeepCopy and Clone claim to do.

We might have a better idea, if we can actually see what the Property Set looks like after calling each method, so I setup this two level hierarchy test case.

var ps=theApplication().NewPropertySet();
var psChild1=theApplication().NewPropertySet();
var psChild2=theApplication().NewPropertySet();
ps.SetType("Parent");
ps.SetValue("ParentValue");
ps.SetProperty("Mum","Georgette");
ps.SetProperty("Dad","George");
psChild1.SetType("Child 1");
psChild2.SetType("Child 2");
psChild1.SetProperty("Name","Bob");
psChild1.SetProperty("Gender","Male");
psChild2.SetProperty("Name","Jane");
psChild2.SetProperty("Gender","Female");
ps.AddChild(psChild1);
ps.AddChild(psChild2);

console.log( "---------------------Original PS---------------------\n"+
util.PSToStr(ps) );
var psCopy=theApplication().NewPropertySet();
psCopy.Copy(ps);

console.log( "---------------------Copy Method---------------------\n"+
util.PSToStr(psCopy) );
var psClone=ps.Clone();
console.log( "---------------------Clone Method---------------------\n"+
util.PSToStr(psClone) );
var psDeepCopy=theApplication().NewPropertySet();
psDeepCopy.DeepCopy(ps);
console.log( "---------------------DeepCopy Method---------------------\n"+
util.PSToStr(psDeepCopy) );


This is the result:



Interesting, they all output the same result.

To get a deeper understanding of what is happening, we need to inspect the function bodies for these methods to understand what the intent is for these different methods.

Copy Function Definition

function JSSPropertySet_Copy(old) {
var i;
var oldChild;
var name;
if (this.axObj != null) {
        if (old == null)
                return (false);
        this.axObj.Copy(old.axObj); // this should return a value
        return (true);
}
this.Reset();
if (old == null)
                return (false);
name = old.GetType();
if (name != null && name != "")
        this.SetType(name);
value = old.GetValue();
if (value != null && value != "")
        this.SetValue(value);
for (name in old.propArray)
        this.SetPropertyStr(name, old.propArray[name]);
for (i = 0; i < old.childArray.length; i++) {
        oldChild = old.childArray[i];
        if (oldChild == null)
                break;
        newChild = new JSSPropertySet();
        newChild.Copy(oldChild);
        this.AddChild(newChild);
}
return (true);  
}


"Copy" uses the current Property Set, and recursively walks through the Input PS, and copies it, to itself. It also looks like it has some legacy code from its prior ActiveX days


DeepCopy Function Definition

function JSSPropertySet_DeepCopy(inputPS) {
var i;
var name;
var value;
var oldPS;
var newPS;
if (inputPS instanceof JSSPropertySet) {
        name = inputPS.GetType();
        if (name != null && name != "")
                this.SetType(name);
        value = inputPS.GetValue();
        if (value != null && value != "")
                this.SetValue(value);
        for (name in inputPS.propArray)
                this.SetPropertyStr(name, inputPS.propArray[name]);
        for (i = 0; i < inputPS.childArray.length; i++) {
                oldPS = inputPS.childArray[i];
                if (oldPS == null)
                        break;
                newPS = oldPS.Clone();
                this.AddChild(newPS);
        }
        return true;
}
return false;
}


"DeepCopy" preserves the current Property Set, and relies on "Clone" to perform the recursive copy.

Clone Function Definition

function JSSPropertySet_Clone() {
var i;
var name;
var value;
var dup = new JSSPropertySet();
name = this.GetType();
if (name != null && name != "")
         dup.SetType(name);
value = this.GetValue();
if (value != null && value != "")
        dup.SetValue(value);
for (name in this.propArray)
        dup.SetPropertyStr(name, this.propArray[name]);
for (i = 0; i < this.childArray.length; i++) {
        oldChild = this.childArray[i];
        if (oldChild == null)
                        break;
        newChild = oldChild.Clone();
        dup.AddChild(newChild);
}
return (dup);   
}


"Clone" works slightly different, because it copies itself, and returns a Property Set.

We can now conclude that the intent is that all 3 methods (Copy, DeepCopy, Clone) performs a copy of the entire Tree.

Summary

1. Copy performs a reset on the current Property Set, and clears all variables before performing a Copy on the Input Property Set

2. DeepCopy merges the Input Property Set into itself, combining the properties of both Property Sets

3. Clone copies itself, and is assigned to another Property Set



While we can see that DeepCopy is different. Functionally Clone and Copy create a Fully Copy, so why did Siebel Invent Clone? The mystery thickens.

Maybe a more complex Property Set and some performance timings will reveal something interesting.

Intermission

By now you know the difference between Copy, DeepCopy and Clone, but there is still one more major twist on the plot.

There is also more script samples to come, and I don’t want you to overdose you on code. If you’ve already OD’d, you'll have some time to recover. Stay tuned for more Impossible Siebel.

Browser Script: PropertySet Copy() Syntax

The author of the following post rasied a simple question.

"The following syntax to copy propertyset is not working in browser script.please let me know the correct syntax to use.

PropSet2 = PropSet1.Copy()"


The syntax is correct, but it doesn't hurt to consult the "Bible" for more details. Bookshelf confirms that the syntax is correct for Browser Script.

The example provided in bookshelf is:
var oPropSet1;
var oPropSet2;
oPropSet2 = oPropSet1.Copy();

However, the example is slightly incorrect, because the Property Set object is not instantiated.



But even if we correct it, there is still an error.



What if we try the Copy method on the destination object, and pass in the source object?



Bingo, it works.

Lets try a more substantial Property Set.


var ps=theApplication().NewPropertySet();
var psChild1=theApplication().NewPropertySet();
var psChild2=theApplication().NewPropertySet();
ps.SetType("Parent");
ps.SetValue("ParentValue");
ps.SetProperty("Mum","Georgette");
ps.SetProperty("Dad","George");
psChild1.SetType("Child 1");
psChild2.SetType("Child 2");
psChild1.SetProperty("Name","Bob");
psChild1.SetProperty("Gender","Male");
psChild2.SetProperty("Name","Jane");
psChild2.SetProperty("Gender","Female");
ps.AddChild(psChild1);
ps.AddChild(psChild2);
var ps2=theApplication().NewPropertySet();
ps2.Copy(ps);




As we would expect, the Copy method does work, but it is not correctly documented or built incorrectly, it is also inconsistent with the same object in eScript.

This test was performed on 8.1.1.5, 8.1.1.10, and the author of the original problem posted the problem back in 2008, which goes back at least 5 years. Isn’t amazing that a defect has been in the wild for so long without being detected?

Open UI: Property Set - New Structure

As with any new Siebel release, I’m sure we all rush enthusiastically to see what’s the latest feature in Bookshelf. Since Open UI was released I like to keep a copy of Bookshelf below my pillow, and read a new leaf every night.

Last night I reached the new Open UI chapter on Property Sets. It describes that Property Sets have a new structure under Open UI.

New Structure

A traditional property set consists of four parts: Type, Value, Properties, Child Property Set



An Open UI Property Set, exposes 4 new interesting parts to this existing structure


This is documented in Bookshelf here



childArray

As Bookshelf states, it’s just an Array that holds references to all the child Property Set objects. It stores the same object that is returned by GetChild.

childEnum

This looks like it should contain a count of child Property Sets. But it incorrectly displays 0, as can be seen in our test case below.

propArray

If you follow the namesake, you would think this property is an Array like childArray, but read the Bookshelf description carefully, and you will see that this is actually an Object ...but inspect the "Object" in the browser more carefully, and you’ll see that it is actually an Array!. It’s like asking a couple of people on the street for the location of a landmark, and they point in opposite directions!

So which source do you believe? Bookshelf or the Browser inspector? In this case both, functionally the former, but technically the latter.

What we have here is an Associative Array, Arrays and Objects can be used in similar ways. Arrays are usually indexed, and the standard way to iterate through them is a "for" loop. Objects don’t have an index, but use keys, and they are enumerated using the "for in" loop.

var myArray=["Dieter","Thomas"];
var myObject={"Composer":"Dieter","Singer":"Thomas"};
var myAssocArray=[];
myAssocArray["Composer"]="Dieter";
myAssocArray["Singer"]="Thomas";

console.log( "for i myArray" );

for(var i=0;i<myArray.length;i++){
    console.log( "\t"+i+" "+myArray[i] );
}

console.log( "for in myObject" );
for(var key in myObject){
    console.log( "\t"+key+ " "+myObject[key] );
}
console.log( "\t"+"myObject.length=" + myObject.length );

console.log( "for in myAssocArray" );
for(var key in myAssocArray){
    console.log( "\t"+key+ " "+myAssocArray[key] );
}
console.log( "\t"+"myAssocArray.length=" + myAssocArray.length );

Result:



Note: the Array constructor doesn't allow us to create Arrays with default keys. You have to create the Array first, then add the keys manually.

The bottom line is that an Associative array behaves like an object, but it is not intuitive and is even considered harmful. Make a mental note about this property and treat it like an Object.

With traditional Property Sets, you would need to use the following to iterate through the properties.

var propName = oPS.GetFirstProperty();
while (propName != "")  {
      try  {
           var propValue = oPS.GetProperty(propName);
           console.log(propName+"="+propValue);
      }catch (e)  {}
      propName = oPS.GetNextProperty();
}


With Open UI Property Sets, you can now do the following.

for(var key in oPS.propArray){
      console.log(key+"="+oPS.propArray[key]);
}


Feels weird, but it works, and Property Sets are now less clunky to deal with. Nice!

propArrayLen

This contains a count of propArray



To visualise how these new Property Sets are represented, the following test case reveals the structure, and allow us to inspect the object in the browser.

var ps=theApplication().NewPropertySet();
var psChild1=ps.Clone(),psChild2=ps.Clone();
ps.SetType("Parent");
ps.SetValue("ParentValue");
ps.SetProperty("Mum","Georgette");
ps.SetProperty("Dad","George");
psChild1.SetType("Child 1");
psChild1.SetProperty("Name","Bob");
psChild1.SetProperty("Gender","Male");
psChild2.SetType("Child 2");
psChild2.SetProperty("Name","Jane");
psChild2.SetProperty("Gender","Female");
ps.AddChild(psChild1);
ps.AddChild(psChild2);


Result in Fire fox:



This screen shot only shows the additional elements as documented by Bookshelf. The new Property Set is backwards compatible with the old version and traditional Property Set API calls such as GetChild, and GetProperty still works (mostly).

In the next article on Open UI Property Sets, we'll visit some of the new methods, and expose some of the broken methods!.

Stay tuned for the next episode of Open UI: Property Set - Methods

Next Article