Tuesday, August 3, 2010

Correction to Code - from Getting Started with SIFs

In the last post I had a couple of lines of code added to the browser script and server script of the application that you use... Neel posted a comment to say he was having some trouble using 8.1 - I'm not sure that this error is to blame, but it can't be helping!

Hopefully this correction will help get the code at least running.

So, the correct code for the server script declarations section of the application you are using should be:

var oBS:Service = this.GetService("MFramework");
oBS.InvokeMethod ("BuildFramework", this.NewPropertySet(), this.NewPropertySet());

while the following is the correct code for the new method in the browser script of the application you are using:

function BuildFramework()
{
    var bsUi = theApplication().GetService("MFramework");
    bsUi.InvokeMethod("BuildBsFramework",theApplication().NewPropertySet());
};

I've added this. and theApplication(). in front of the calls to the NewPropertySet() method, which hopefully will help things run.

Monday, July 19, 2010

Getting Started with SIFs

Having downloaded the source files this should help get things started:

1. Import the business service and the workflow process.

2. Add the following line to the config file in the appropriate place (in the [SWE] section)

ClientBusinessServiceXX  = "MFramework"

3. Check in and Deploy the WF Process "MFramework Service Wrapper WF" (if you want to use the asynch business service calling feature).

4. Add the following to the server script declarations section of the application you are using:

var oBS:Service = this.GetService("MFramework");
oBS.InvokeMethod ("BuildFramework", NewPropertySet(), NewPropertySet());

5. Add the following as a new method in the server script of the application you are using:


function FrameworkHandle()
{
    return this.GetService("MFramework").frameworkHandle();
}

This acts as a wrapper for BCs, BSs etc to get a reference to the Framework object.

6. Add the following to the browser script declarations section of the application you are using:

setTimeout("BuildFramework()", 1000);

7. Add the following as a new method in the browser script of the application you are using:


function BuildFramework()
{
    var bsUi = theApplication().GetService("MFramework");
    bsUi.InvokeMethod("BuildBsFramework",NewPropertySet());
};

8. Compile and generate browser scripts.

Run the application.

To test we'll run some of the browser side functionality (which in turn runs the server side, so is a good way to smoke test)... so when the application is running copy and paste the following into the address bar:

javascript:alert(top.oFramework.BusComp.getRecord("Employee.Employee", "0-1", ["Full Name"])["Full Name"]);
//

Hopefully you'll get an alert with "SADMIN SADMIN" here, if so, great, it works!

Some more tests:

javascript:alert(top.oFramework.BusComp.getRecord("Employee.Employee", "0-1", ["Full Name"]).Full_Name);
//

Same result as above, just accessing the value in a subtly different way.

javascript:alert(top.oFramework.Lov.getDescription("LOV_TYPE","LOV_TYPE"));
//

Here you should get a description of the LOV_TYPE lov.

javascript:alert(top.oFramework.BusComp.getRecord("Employee.Employee", "0-1","").Created);
//

So that should give the Date of when the SADMIN Employee record was created.

javascript:alert(top.oFramework.BusComp.getRecord("Employee.Employee", "0-1","").Created.toDb());
//

Same as last test but displays in DD/MM/YYYY format. This makes use of two features of framework - new methods added to JS datatypes, and optional typing of fields in the BusComp.getRecord() method.

So hopefully that all works, let me know if not and I'll try and figure out why.

For info., I use 7.7 and the ST engine - I did have strong typing in the code, which I have removed to allow this work in the T engine, but there may be some  other problems with the way I use the JS objects, so let me know if you've any issues or thoughts.

And let me know if it works too, of course!

Matt

Framework SIFs

As promised here are some SIFs that should hopefully make getting this running a little bit easier, or at the very least make the code a little easier to follow.


Source Code


Contained in the .zip file above are a few files, which I'll briefly run through:


1. MFramework.sif


This is a SIF file of the business service that contains both the server side (eScript) elements of the Framework, and the corresponding Browser Script side. This business service is contained in a project called "MFramework".


2. MFramework_BS.sif


This is a SIF of just the business service, without containing project, if you want to import into a different project.


3. MFramework Service Wrapper WF.xml


This is a workflow process used by the asynch business service calling. As it currently is, this is contained in the project called MFramework.


4. MFramework_bs.js


If you'd prefer just to view the code in your favorite editor this file is the entire browser script side of the business service...


5. MFramework_ss.js


... while this is the server eScript of the business service.


I'll create a further post to give a quick "how to" list to get started with this shortly...


Matt

Saturday, July 10, 2010

Coming soon...

Folks, just thought I'd post a quick note about some items in the pipeline... I'm on holidays at the moment, so haven't been near a computer in a while.


Puli posted an interesting comment on the lack of security on the browser script side - remember this passes a call down to the server side, where an eval is used. Well, with some playing around I got some unauthorised code to run. I'm not sure what mischief could be caused by this, but this is an unexpected problem - I've some examples of this in action, and a (hopefully) straightforward fix.

The Framework uses a technique described in Oracle Doc Id 579955.1 - which basically shows how to directly run methods on a cached business service (avoiding the dreaded InvokeMethod and property sets!). It has been suggested to me that caching business services can cause problems in later versions of Siebel. (I'm using Linux at the moment, and the Oracle support site seems to take exception to this for some reason and won't allow me in so I can't check!) - I'll post an alternative way to make the Framework work without the BS caching (and no visible difference to the callers of the Framework).

Finally, the SIFs - a good few comments saying the code is great, which is nice, but not necessarily straightforward to cut and paste, and expect to get working. I have some SIFs nearly ready to go, I just need to remove some specific code from a few places... I do have these, but not actually tested them yet, so once tested will post up.

Matt

Friday, June 18, 2010

From Browser Side, Down, then Back Up

As a topic that will cover a lot of the features of the Framework I thought tracing a call from browser script down to the server side then back up would be interesting. As an example I'll take getting a field value from applet browser script.

Now this is something that should be really straightforward - but isn't. Really, you're limited to only the fields that are displayed on the applet - there are some tricks to get other fields (hidden columns, anyone?) but it always seemed that this should be easier. Then what if you want a field from a BC not on the applet?

This Framework offers a way to do this - and good news - it can be done in a line! (We'll, a line is added to your applet browser script, a few more lines of code get used)

Just for simplicity we'll display a personalised alert to the current user.

So we add the following line to the applet's browser script:

alert("Hello there " + top.oFramework.BusComp.getRecord("Employee.Employee", theApplication().LoginId(), ["Full Name"]).Full_Name);

And that's it.

This will do what it looks like -display a "Hello there" message to the current user (querying the Employee BC). This will run on any applet (in fact any browser script anywhere) and doesn't need to have the "Full Name" field shown. It doesn't even need to be anything to do with the Employee BC!

Ok - how does this work?

Well starting in the application browser script here a call to a business service is made (the browser script side of the framework, here. Now the method called, BuildBsFramework() is empty. The real work is done in the declarations section. There are two lines here:

ExtendJsObjects();
top.oFramework = new Class_Main();


These will be discussed in a later post, but in summary the first extends JS objects (currently String and Date) while the second instantiates a Class_Main object, and attaches to top, naming it "top.oFramework"

Within the Class_Main there is an object BusComp that encapsulates functionality relating to Business Components - in the case of the Browser Script version here all it does is expose several wrappers - so getRecord is one. Again, we'll discuss the methods (and their signatures in a later post) - for the moment let's look at what this does:

getRecord: function () { return this.methodWrapper("BusComp.getRecord", arguments); },


So this calls a second method; methodWrapper; with an argument "BusComp.getRecord", then the arguments array passed into it - so: "Employee.Employee", theApplication().LoginId(), ["Full Name"];

methodWrapper is shared by the various browser script objects:

function Method_Wrapper(sName, vArguments)
{
 var oArguments =[];

 oArguments[0] = sName;

 for(var x=0;x<vArguments.length;x++)
 {
  oArguments[x+1] = vArguments[x];
 }

 return top.oFramework.callServerFramework(oArguments);
}


so this takes the two arguments it gets - the name and the arguments array and essentially joins them into one (arguments does not seem to be a "true" array object, so the above technique is used in place of using push() or concat() say.)

Now the callServerFramework method of the Framework object is used. Within this there is essentially a normal call using a business service (so don't forget to add its name to the config file, but worry not, this is the last time you'll ever have to do this!). There are a couple of interesting lines:

oInputs.SetProperty("Method", oArguments.shift());


So the first argument (which in this case is "BusComp.getRecord" is assigned to an input property called "Method"). The next line of interest is:

oInputs.SetProperty("Arguments", this.toSource(oArguments));


So this sets a second input property called "Arguments" to be the return of "toSource()" when passed the remainder of the arguments array (which in this case would be "Employee.Employee", theApplication().LoginId(), ["Full Name"])

Here is the source of toSource():

toSource: function (obj)
{
    var sReturn = ""; 
    if (typeof(obj)=="string") // strings - need to quote, also replace ' with \'
    {
        sReturn = "\"" + obj.replace(/\'/g,"\\\x27") + "\"";
    }
    else if (typeof(obj.shift)=="function") // arrays
    {
        sReturn += "[";
        for (var i=0; i<obj.length; i++)
        {
            sReturn += this.toSource(obj[i]) + ", ";
        }
        sReturn = (sReturn.length==1) ? sReturn + "]" : sReturn.substr(0, sReturn.length-2) + "]";
    }
    else if (typeof(obj.getTime)=="function") // date
    {
        sReturn = "new Date(\"" + obj + "\")";
    }
    else if (typeof(obj)=="object") // objects - loop through properties
    {
        sReturn += "{";
        for (var x in obj)
        {
            sReturn += (x.indexOf(" ")>-1) ? "\"" + x + "\":" : x + ":" ;
            sReturn += this.toSource(obj[x]) + ", ";
        }
        sReturn = (sReturn.length==1) ? sReturn + "}" : sReturn.substr(0, sReturn.length-2) + "}";
    }
    else // number, boolen, function will be left
    {
        sReturn = "" + obj;
    }
    return sReturn;
}


What this does is take any JS object and convert it into its source: a String. (So an object that has a property called "Name" with a value of "Value" and a second property called "Array" that has the value of an array with 1,2 and 3 in can be represented as follows:
{Name:"Value", Array:[1,2,3]}


By representing an object in this manner it doesn't matter how complex the object is: it will be represented as a single string. This means that every call can ultimate share a common server side method - no matter what its input looks like, the input the server side method will always be a string representing the method name and a second representing the arguments.

On the server side the method RunFrameworkMethod the important logic is:
var sFrameworkMethod = Inputs.GetProperty("Method"); //Framework method to run
var sArguments = Inputs.GetProperty("Arguments"); //String of arguments
var oReturn; //Return value, may be object or string

//Strip [] from around arguments
sArguments = sArguments.substring(1, sArguments.length-1);

// clear stack
o_LocalFramework.clearStack();

//Build up command: oFramework.method(Arg1, Arg2,...);
var sCommand = "oReturn = o_LocalFramework." + sFrameworkMethod + "(" + sArguments + ");";

//Run command
eval(sCommand);

//If an object is returned, convert to string and set return type to Object
if(typeof(oReturn.shift) == "function" || typeof(oReturn) == "object")
{
 Outputs.SetProperty("Return Type", "Object");
 Outputs.SetProperty("Return Value", o_LocalFramework.toSource(oReturn));
}
//Otherwise return value and set return type to Primitive
else
{
 Outputs.SetProperty("Return Type", "Primitive");
 Outputs.SetProperty("Return Value", oReturn);        
}


take a look at this line:
var sCommand = "oReturn = o_LocalFramework." + sFrameworkMethod + "(" + sArguments + ");";


so we've built a variable called sCommand that in this example case will be:
"oReturn = o_LocalFramework.BusComp.getRecord("Employee.Employee", theApplication().LoginId(), ["Full Name"]);"


The next line of interest is:
eval(sCommand);


So very, very, simple, yet very, very powerful - this runs our server side call for us.

Now while eval may well be "evil", and allows us run anything at all, we've kept some of the "evil" in check here in the way we have built the command we are running - it always starts with "o_LocalFramework." (rather than allowing the caller specify the full command, which adds some safety. So what we'll run will be some of the server side function.

So we have a variable, oReturn, which is the return from: o_LocalFramework.BusComp.getRecord(...) - so the server-side framework method. (Again, this will be discussed in a later post). getRecord returns a JS object. For the minute just assume one of the properties this object has contains the name of the Employee record queried; the property is called "Full Name" and the value "MattW".

The following will be run:

Outputs.SetProperty("Return Type", "Object");
Outputs.SetProperty("Return Value", o_LocalFramework.toSource(oReturn));


The first property indicates that the return has a complex type, the second is an echo of the browser-side toSource() from earlier - so the object is flattened into a single string. This allows us use the same method for calling any number of methods - we're not limited to a specific return type.

Back in callServerFramework() in the browser side Framework we're onto this:

return this.toObject(oOutputs.GetProperty("Return Value"));


So the return property (a JS object in string form) is passed into a method called toObject. Again we're using the power of eval:

var oT = eval('(' + source + ')');


What we have now is oT which will be a proper JS object, as if we'd built it here in browser script, so we can access its attributes in the usual way, so (remembering our assumption) our original line:

alert("Hello there " + top.oFramework.BusComp.getRecord("Employee.Employee", theApplication().LoginId(), ["Full Name"]).Full_Name);


is equivalent to:

alert("Hello there " + obj.Full_Name);


which in this example will be:

alert("Hello there MattW");


Great - from BS down, and all the way back.

(yes, yes, MattW isn't a *full name*, but this is an example!)

So hopefully that all makes sense. Next post I'll have a look at what in some of the server side logic - so BusComp.getRecord may be a good place to start.

Matt

Formatting Tweak

I've re-formatted the earlier posts to make the code clearer, hopefully this has helped readability.

So far this remains by and large a code dump, little explanation yet as to what's going on, and how to use the Framework... this will be added in time.

One of the first items that I think may be useful is an explanation of how the Browser side of the Framework works: the same objects, with the same method signatures and same returns (including datatypes) are available on the browser side as the server side. How has this been achieved?

Thursday, June 17, 2010

Some examples of use

So, a lot of code published with no explanation (so far). In the meantime here are some examples of how the Framework can be used:

Server-Side add the line:
var oFramework = TheApplication().GetService("My Framework").frameworkHandle();

to the declarations section of the BC/BS.

1. Query a BC
So let's get the name of the currently logged in user
var sFullName = oFramework.BusComp.getRecord("Employee.Employee", TheApplication().LoginId(), ["Full Name"])["Full Name"];

*a little flavour of what's to come in the explanation - the Framework returns JS objects to represent complex types (e.g. a record, property set) - so the ["Full Name"] gets this property from the object.

2. Query a child BC
So here we're going to get the Contact records relating to a particular Opportunity, then loop through them (just for illustration). Here BusComp.getRecords returns an array of JS objects.
var oOpty = oFramework.BusComp.getRecord("Opportunity.Opportunity", "1-123F4", "", true);
var aoBorrower = oFramework.BusComp.getRecords("Opportunity.Contact", "", ["First Name", "Last Name", "Login Name"]);

for (var i = 0; i < aoBorrower .length; i++)
{
// just to illustrate logic - build a string
var sString = aoBorrower[i].First_Name + " " + aoBorrower[i].Last_Name + " has a login of " + aoBorrower[i].Login_Name ;
...
...
}

3. Get the "Description" field from an LOV:
var sDescription = oFramework .Lov.getDescription("OCCUPATION","Professional");

4. Run a BS method:
var bIsValid = oFramework .Service.run("My System Process Service.ValidatePpsn", ["Ppsn"], ["4576554F"]).IsValid;

* one thing this has encapsulated is the building of propertysets - the input set here would have one property, "Ppsn" with a value of "4576554F", then in the output set there is one value returned called "IsValid".

If more than one property were returned (say a second called "Message" was present) in the output we could access them both using the JS object:
var oReturn = oFramework .Service.run("My System Process Service.ValidatePpsn", ["Ppsn"], ["4576554F"]);
var bIsValid = oReturn.IsValid;
var sMessage = oReturn.Message;


* to make the BS method run in the background simply use the Service.runAsynch method in place of the Service.run; same inputs, although output is limited to a single boolean.

oFramework.Service.runAsynch("My System Process Service.ValidatePpsn", ["Ppsn"], ["4576554F"]);


For the asynch to work there is a workflow process used for this. This hasn't been posted, so this is for illustration only)

5. Get a system preference:
var sMyPref = oFramework.Utility.getSystemPreference("My System Preference");


6. Send an Email:
(where sEmail is the text, sAttachFile1 and sAttachFile2 are two files to be attached)

oFramework.Utility.sendEmail("Notification From the System",
"",
"Support Team Mailing List",
"",
sEmail,
sAttachFile1 + "*" + sAttachFile2);


7. See if a record (here, taking an LOV for an example) has been updated since a certain date (held here in a system preference):

var sLastRunDate = oFramework.Utility.getSystemPreference("Op Last Run Date");
var oLovRecord = oFramework.BusComp.getRecord("List Of Values.List Of Values","[Type]= 'OCCUPATION'", ["Name","Value"], true);

if (oLovRecord.Updated.compare(">",sCheckParamDate))
{
...


* this gives a sample of some of the Framework features, e.g.: typing of fields from the query (so Updated is a date), Date has a method added to it to compare it against a string representing another date.

8. Some other new Date features:
var sEuDateString = new Date().toDb();
var sUsDateString = new Date().toUs();
var sIsoDateString = new Date().toIso();
var sTimeStamp = new Date().toTimeStamp();

var dDate = new Date();
dDate.setFromString("25/12/1989");


9 Logging:
There are two aspects to logging - the ability to log as things happen. I've removed the code itself to write to a custom CX_ table, but to log from anywhere the following syntax is used:

oFramework.Utility.logMessage(type, code, short description, source name, source type,  source function name, long detail string);



So, for example, in a catch block in a BS PreInvoke method we could have:

oFramework.Utility.logMessage("Error", e.errCode, e.toString(), this.Name(), "BusServ", "Service_PreInvokeMethod(" + MethodName + " ", sDebug);


The second aspect of logging is provided by the Framework object to show its own operations (not just errors) so

var sFeedback = oFramework.getStack();


returns the internal log of the Framework. An example could be:

[UID:20100616162835716]
[20100616163956151] *** START 'BusComp.getRecords' ***
[20100616163956151] Search:1-A3VYMH
[20100616163956151] Reset Context:true
[20100616163956151] Clearing objects
[20100616163956167] Objects cleared
[20100616163956167] Added BusObject: My Application to the pool.
[20100616163956167] Added BusComp: My Application to the pool.
[20100616163956167] Activated: Name
[20100616163956167] Activated: Id
[20100616163956167] Activated: Created
[20100616163956183] Activated: Updated
[20100616163956183] Search:1-A3VYMH Type:string
[20100616163956183] After Search:
[20100616163956386] Error:A script failed to get the value for field Name because the field was not active.(SBL-EXL-00119)


* each line is preceded with the timestamp (down to millisecond) of when that message was logged. This is handy to see exactly how long a step is taking.

10. Browser-Side:

No need to get any references as the browser-side Framework is attached to the "top" object - available anywere.

The syntax (for the methods exposed by the browser-side) matches their server side counterparts. So:

(query a BC)
var sFullName = top.oFramework.BusComp.getRecord("Employee.Employee","0-1" , ["Full Name"])


(get an LOV's description field)
var sDescription = top.oFramework .Lov.getDescription("OCCUPATION","Professional");



(run a BS method)
top.oFramework.Service.run("My System Process Service.ValidatePpsn", ["Ppsn"], ["4576554F"]);



(get a system preference)
var sMyPref = top.oFramework.Utility.getSystemPreference("My Preference Value");


(get a timestamp)
var sTimeStamp = new Date().toTimeStamp();


I'll follow this up with a post on the mechanism used by the browser side of the Framework, so that the methods have the same arguments, and return the same values (including JS objects) as their server side counterparts.

The Application Brower Script

This is the code in The Application browser script - used to call the browser side version of the framework (which gets attached to the top object, so available everywhere)

// build framework object - will be accessible to all objects via top.oFramework
setTimeout("BuildFramework()", 1000);

function BuildFramework()
{
var bsUi = theApplication().GetService("My Framework");
bsUi.InvokeMethod("BuildBsFramework",NewPropertySet());

top.oFramework.resizeAppWindow();
}

The Application Server Script

This is the script in the Application object. It's used to call the business service (empty method) which will create an instance of the framework (Class_Main) in its declaration section. The Business Service is cached, so we only get one instance of the Framework in memory (per session).`


var oBS:Service = this.GetService("My Framework");
oBS.InvokeMethod ("BuildFramework", NewPropertySet(), NewPropertySet());

function FrameworkHandle()
{
/*------------------------------------------------------------------------------------------
Created: 04/01/2010
Description: Return a reference to the Framework object built by BS
------------------------------------------------------------------------------------------*/
return this.GetService("My Framework").frameworkHandle();
}

The Business Service Browser Script

Ok, here's the browser script from the Business Service. The purpose of the BS is to act as wrappers to the server side - allowing rich features on the browser side. I'll discuss in more detail in a future post - there are security concerns in particular here becuase of what we're exposing that are worth a mention.
ExtendJsObjects();
top.oFramework = new Class_Main();

function BuildBsFramework()
{
// empty method - real work done in declarations section
}

function Class_BusComp(theParent)
{
/*------------------------------------------------------------------------------------------
Created: 01/06/2010
Description: BusComp class
Encapsulate functionality relating to Business Components
-----------------------------------------------------------------------------------------*/

var oBusComp =
{
// member variables
name: "Class_BusComp",
parent: theParent,

// member methods - simple wrappers
methodWrapper: Method_Wrapper,
addRecord: function () { return this.methodWrapper("BusComp.addRecord", arguments); },
getRecord: function () { return this.methodWrapper("BusComp.getRecord", arguments); },
getRecords: function () { return this.methodWrapper("BusComp.getRecords", arguments); },
updateRecord: function () { return this.methodWrapper("BusComp.updateRecord", arguments); },
updateRecords: function () { return this.methodWrapper("BusComp.updateRecords", arguments); }
}

return oBusComp;
}

function Class_IHelp(theParent)
{
/*------------------------------------------------------------------------------------------
Created: 01/06/2010
Description: IHelp class
Encapsulate functionality relating to IHelp
------------------------------------------------------------------------------------------*/

var oIHelp =
{
// member variables
name: "Class_IHelp",
parent: theParent,

// member methods
setToPath: function (sHelpPathName)
{
// code removed for brevity - sets iHelp to a particular path
},

setToRoot: function ()
{
// code removed for brevity - returns iHelp to root menu
},

openPath: function (sHelpPathName)
{
// code removed for brevity - opens iHelp menu window + sets to a particular path
}
}

return oIHelp;
}

function Class_Lov(theParent)
{
/*------------------------------------------------------------------------------------------
Created: 16/06/2010
Description: Lov class
Encapsulate functionality relating to LOVs
------------------------------------------------------------------------------------------*/

var oLov =
{
// member variables
name: "Class_Lov",
parent: theParent,

// member methods - simple wrappers
methodWrapper: Method_Wrapper,
get: function () { return this.methodWrapper("Lov.get", arguments); },
getAzValue: function () { return this.methodWrapper("Lov.getAzValue", arguments); },
getDescription: function () { return this.methodWrapper("Lov.getDescription", arguments); },
getEuFlag: function () { return this.methodWrapper("Lov.getEuFlag", arguments); }
}

return oLov;
}

function Class_Main()
{
/*------------------------------------------------------------------------------------------
Created: 05/05/2010
Description: Class definition - can be accessed via top.oFramework
------------------------------------------------------------------------------------------*/

var oMain =
{
// members values
name: "Class_Main",
createdTime: new Date(),
UID: new Date().toTimeStamp(),
aStack: [],

// methods
addToStack: function (sValue)
{
this.aStack.push("[" + new Date().toTimeStamp() + "] " + sValue);
// limit the size of the stack to the last 50 entries
if (this.aStack.length>50)
this.aStack.shift();
},

clearStack: function ()
{
this.aStack = [];
},

getStack: function ()
{
return "[UID:" + this.UID + "]\n" + this.aStack.join("\n");
},

getCreatedTime: function ()
{
return this.createdTime;
},

resizeAppWindow: function ()
{
top.window.moveTo(0,0);
top.window.resizeTo(1280,960);
},

toObject: function (source)
{
/*------------------------------------------------------------------------------------------
Created: 12/05/2010
Description: Rebuild an object from its source representation.
The handling of the _length is to workaround the fact
that Siebel does not allow an object property called "length"
be enumerated using the "for in" construct.

------------------------------------------------------------------------------------------*/

var oT = eval('(' + source + ')');

if (oT._length)
{
oT.length = oT._length;
}
return oT;
},

toSource: function (obj)
{
/*------------------------------------------------------------------------------------------
Created: 11/05/2010
Description: Convert any Javascript object into its source (string) representation
This can be used to pass return objects from the server-side Framework
to the browser side, recursion is used to break down properties of objects
and elements of arrays.

Quirk of Siebel: Arrays seem to have a typeof "function" rather than "object"
as expected. To distinguish between an Array and a real function, check does
it have a method called "shift" - an array will

------------------------------------------------------------------------------------------*/
var sReturn = "";
if (typeof(obj)=="string") // strings - need to quote, also replace ' with \'
{
sReturn = "\"" + obj.replace(/\'/g,"\\\x27") + "\"";
}
else if (typeof(obj.shift)=="function") // arrays
{
sReturn += "[";
for (var i=0; i<obj.length; i++)
{
sReturn += this.toSource(obj[i]) + ", ";
}
sReturn = (sReturn.length==1) ? sReturn + ']' : sReturn.substr(0, sReturn.length-2) + ']';
}
else if (typeof(obj.getTime)=="function") // date
{
sReturn = "new Date(\"" + obj + "\")";
}
else if (typeof(obj)=="object") // objects - loop through properties
{
sReturn += "{";
for (var x in obj)
{
sReturn += (x.indexOf(" ")>-1) ? "\"" + x + "\":" : x + ":" ;
sReturn += this.toSource(obj[x]) + ", ";
}
sReturn = (sReturn.length==1) ? sReturn + '}' : sReturn.substr(0, sReturn.length-2) + '}';
}
else // number, boolen, function will be left
{
sReturn = "" + obj;
}
return sReturn;
},

callServerFramework: function ()
{
/*------------------------------------------------------------------------------------------
Created: 17/05/2010
Description: This method uses a business service to expose server-side Framework methods
to browser script.

------------------------------------------------------------------------------------------*/

var oArguments = arguments[0]; //This will be an array of arguments

//Objects required to call business service
var oBusServ = null;
var oInputs = null;
var oOutputs = null;


try
{
//Set up objects
oBusServ = theApplication().GetService("My Framework");
oInputs = theApplication().NewPropertySet();
oOutputs = theApplication().NewPropertySet();

//Place the first argument in the Method property
oInputs.SetProperty("Method", oArguments.shift());

//Convert the remaining arguments to a string and place in the Arguments property
oInputs.SetProperty("Arguments", this.toSource(oArguments));

//Call server-side Framework
oOutputs = oBusServ.InvokeMethod("RunFrameworkMethod", oInputs);

//Rebuild complex objects and return
if(oOutputs.GetProperty("Return Type") == "Object")
{
return this.toObject(oOutputs.GetProperty("Return Value"));
}
//Return simple objects
else
{
return oOutputs.GetProperty("Return Value");
}

}
catch(e)
{
}
finally
{
//Clear down objects
oArguments = null;
oBusServ = null;
oInputs = null;
oOutputs = null;
}
},

variantToArray: function(pV)
{
return (typeof(pV)=="object" || typeof(pV)=="function" ? pV : [pV]);
}
};

oMain.BusComp = new Class_BusComp(oMain);
oMain.iHelp = new Class_IHelp(oMain);
oMain.Lov = new Class_Lov(oMain);
oMain.Service = new Class_Service(oMain);
oMain.Utility = new Class_Utility(oMain);

return oMain;
}

function Class_Service(theParent)
{
/*------------------------------------------------------------------------------------------
Created: 16/06/2010
Description: Service class
Encapsulate functionality relating to Services
------------------------------------------------------------------------------------------*/

var oService =
{
// member variables
name: "Class_Service",
parent: theParent,

// member methods - simple wrappers
methodWrapper: Method_Wrapper,
run: function () { return this.methodWrapper("Service.run", arguments); },
runAsynch: function () { return this.methodWrapper("Service.runAsynch", arguments); }
}

return oService;
}

function Class_Utility(theParent)
{
/*------------------------------------------------------------------------------------------
Created: 16/06/2010
Description: Service class
Encapsulate functionality relating to Utility
------------------------------------------------------------------------------------------*/

var oUtility =
{
// member variables
name: "Class_Utility",
parent: theParent,

// member methods - simple wrappers
methodWrapper: Method_Wrapper,
getSystemPreference: function () { return this.methodWrapper("Utility.getSystemPreference", arguments); },
setSystemPreference: function () { return this.methodWrapper("Utility.setSystemPreference", arguments); },
logMessage: function () { return this.methodWrapper("Utility.logMessage", arguments); }
}

return oUtility;
}

function ExtendJsObjects()
{
// String methods
String.prototype.spaceToUnderscore = function() {
return this.replace(/\s/g,"_");
}
String.prototype.hasSpace = function() {
return this.match(/\s/);
}
String.prototype.trim = function() {
return this.replace(/^\s+|\s+$/g,"");
}
String.prototype.trimLeft = function() {
return this.replace(/^\s+/g,"");
}
String.prototype.trimRight = function() {
return this.replace(/\s+$/g,"");
}
String.prototype.inList = function(vA) {
for (var i=0; i<vA.length; i++)
if (vA[i]==this.valueOf())
return true;

return false;
}
// Date methods
Date.prototype.compare = function() {
var op = (arguments.length == 2 ? arguments[0] : "==" );
var pV = (arguments.length == 2 ? arguments[1] : arguments[0] );
var bReturn = false;
var d = new Date();
d.setFromString(pV);

if (op=="!=")
bReturn = this.toString()!=d.toString();
else
{
if (op.indexOf("=")>-1)
bReturn = (this.toString() == d.toString());

if (op.indexOf("<")>-1)
bReturn = bReturn || (this < d);

if (op.indexOf(">")>-1)
bReturn = bReturn || (this > d);
}
return bReturn;
}
Date.prototype.toIso=function() {
// return date in the format YYYYMMDD
var YYYY, MM, DD, M, D; // variables to hold parts of date

YYYY=this.getFullYear()+""; // convert to string
MM = (M=this.getMonth()+1)<10?('0'+M):M; // add 0 if 1-9 to make MM format
DD = (D=this.getDate())<10?('0'+D):D; // add 0 if 1-9 to make DD format

return YYYY+MM+DD;
}
Date.prototype.toDb=function() {
// return date in the format DD/MM/YYYY
var YYYY, MM, DD, M, D; // variables to hold parts of date

YYYY=this.getFullYear()+""; // convert to string
MM = (M=this.getMonth()+1)<10?('0'+M):M; // add 0 if 1-9 to make MM format
DD = (D=this.getDate())<10?('0'+D):D; // add 0 if 1-9 to make DD format

return DD+"/"+MM+"/"+YYYY;
}
Date.prototype.toUs=function() {
// return date in the format MM/DD/YYYY
var YYYY, MM, DD, M, D; // variables to hold parts of date

YYYY=this.getFullYear()+""; // convert to string
MM = (M=this.getMonth()+1)<10?('0'+M):M; // add 0 if 1-9 to make MM format
DD = (D=this.getDate())<10?('0'+D):D; // add 0 if 1-9 to make DD format

return MM+"/"+DD+"/"+YYYY;
}
Date.prototype.toTimeStamp=function() {
var hh,mm,ss,mmm,h,m,s,ms;

var ms = this.getMilliseconds();
var s = this.getSeconds();
var m = this.getMinutes();
var h = this.getHours();

var mmm = (ms<10?('00'+ms):ms<100?('0'+ms):ms);

hh = h<10?('0'+h):h==24?'00':h;
mm=m<10?('0'+m):m;
ss=s<10?('0'+s):s;

return this.toIso() + hh+mm+ss+mmm;
}
Date.prototype.setFromString = function(pV) {
var aTs = pV.split(" ");
var aD = aTs[0].split("/");
var aT = (aTs.length > 1) ? aTs[1].split(":") : [0,0,0];

this.setFullYear(aD[2], aD[1]-1, aD[0]);
this.setHours(aT[0], aT[1], aT[2]);
}
}

function Method_Wrapper(sName, vArguments)
{
/*------------------------------------------------------------------------------------------
Created: 01/06/2010
Description: passes arguments + object.method name to server side
------------------------------------------------------------------------------------------*/
var oArguments =[];

oArguments[0] = sName;

for(var x=0;x<vArguments.length;x++)
{
oArguments[x+1] = vArguments[x];
}

return top.oFramework.callServerFramework(oArguments);
}

function Service_PreInvokeMethod (methodName, inputPropSet, outputPropSet)
{
sReturn = "ContinueOperation";

try
{
switch ( methodName )
{
case "BuildBsFramework":

BuildBsFramework();

sReturn = "CancelOperation";
break;

default:
break;
}
}
catch(e)
{
}
finally
{
}

return (sReturn);
}

Introduction

Promopted by discussions about the existance of other framework examples, but with no code published at the time, and a personal gripe with Siebel in the way that scripting can, at times, be a little clunky. I looked at code and noticed that quite often a good chunk of what we write is to do with the "plumbing", rather than the actual logic. In a few cases what we're attempting to do has 4 or 5 lines of the plumbing code, and the important bit is buried amongst this in a single line. Another problem I saw was the repetion - while C++, Java, even JS offer many ways to write once, use many times, I noticed that the same logic gets written over and over - even simple things - looking up field values on LOVs, manipulating dates.

I thought I'd try to come up with my own take on the idea.

Some issues/thoughts I had along the way:
1. whatever way the Framework was implemented should not be version dependent, or depend on either the T or ST engine
2. make it easier to do repeat tasks
3. should work with existing code, not just greenfields - for me attaching code to Array or Object prototypes wasn't ideal as existing code that used for...in would return extra elements
4. memory use
5. speed - as the Framework code is generic it is generally longer than the equivalent original - so it shouldn't have a noticeable performance hit
6. should offer both browser side and server side functions: on the browser side things that are trickier than they should be (get an LOV, get a system preference, even get a field value) should be simplified
7. could use JS objects to offer rich features


There are 4 parts to this that will be posted - server script from a business service (which is the bulk of the framework), browser script from the business service (offers a cut down feature set in brower script), applicaion server script - a call to create the framework, application browser script - a call to create the browser side framework.

I'll post these up initially, then will add discussions to various apects of the framework and the approach later.

Matt