Showing posts with label Open UI. Show all posts
Showing posts with label Open UI. Show all posts

Open UI: Protocol Handler

Getting rid of Active X usage is a priority on Open UI projects, because it allows Siebel customers to embrace more modern browsers, and remain compliant into the future, but one of the last bastions of Active X support in a Siebel Open UI project stems from a requirement to launch executables inside an internal domain.

Launching a program on the users local machine, requires browser script code similar to the following to be implemented.

var sCmd = "notepad.exe text.txt";
var shell = new ActiveXObject("WScript.Shell") ;
shell.Exec(sCmd);

As part of an Open UI upgrade review to remove browser script, I recommended to a Siebel customer that they implement a custom protocol handler to get around the above dependence on Active X.

Protocol handlers are cross platform, and are supported by the major browsers. Although the term protocol handler is obscure amongst general users, its usage is quite prolithic. Here are some common examples of protocols which are handled by browsers

http://
https://
ftp://

The above protocols are trapped and handled internally by the browser, while other protocols are passed down to the OS to be handled by the default program.

mailto://
itmss://

When mailto:// is typed in to the browser address bar, it launches the default program which is registered to handle emails. Apple users will also recognize the second protocol itmss://, as it is used to launch iTunes, the subtle suggestion here is, we could use the same technique to launch any executable.

Each platform has its own method of implementing protocol handlers, so if you are lucky enough to support mac users on Siebel, you’ll need to look into the appropriate vendor documentation. The MSDN reference provides a good digest for those wanting to implement protocol handlers in windows.

https://msdn.microsoft.com/en-us/library/aa767914(v=vs.85).aspx

To implement a handler for a new protocol named jle://, a simple reg file can be created and imported to into the registry.

Windows Registry Editor Version 5.00
 
[HKEY_CLASSES_ROOT\jle]
@="URL:jle protocol"
"URL Protocol"=""
 
[HKEY_CLASSES_ROOT\jle\DefaultIcon]
@="C:\Apps\protocol_handler.bat"
 
[HKEY_CLASSES_ROOT\jle\shell]
 
[HKEY_CLASSES_ROOT\jle\shell\open]
 
[HKEY_CLASSES_ROOT\jle\shell\open\command]
@="C:\Apps\protocol_handler.bat" "%1"

Replace “jle” with your own protocol, and substitute the path to “protocol_handler.bat” according to your local path. “protocol_handler.bat” is a test file that can be used debug the arguments, to see what gets passed to the invoked program, but you may want to use something more sophisticated than a batch file.

rem protocol_handler.bat file
@echo off
echo %1
pause


Once thats setup, type the the new protocol + program arguments into the browser address bar and press enter. Here’s a screenshot of the protocol handler in action.



When designing the protocol handler, keep in mind that you may want to implement a lookup system, to maintain the flexibility of invoking different programs, without having to register new protocols.

This approach was used on an Open UI upgrade to negate the dependence on ActiveX in IP2012+. If you are looking to solve similar requirements on your project, it is important to keep the following points in mind.

Pros Cons

  • No Active X
  • Works across all browsers, and platforms
  • Does not need elevated privileges
  • Launch only registered programs

  • Requires desktop deployment
  • Requires non Siebel expertise


  • Open UI: Testing with Selenium

    Selenium is a browser automation tool, that allows web projects to automate repetitive tasks. As an early pioneer of Open UI, I found this new capability to be incredibly useful for achieving continuous integration. I gave readers a hint of this last year, in the article "Open UI - Build Process", which left readers with a list of ingredients, and a plan to manage their build automation.

    Now that more Siebel projects around the world are on embarking on upgrading their Siebel Applications to Siebel Open UI. Its about time we advance this concept into something more tangible that Clients can benefit from.

    Picture this... as part of the Open UI upgrade, you are required to navigate to 1000+ views in Siebel and test for WCAG defects in all 3 major browsers. That isn't so exciting.

    Testing for Open UI defects requires a high level of thoroughness to ensure that entire your Application is compliant, and remains compliant in the future. The move to Open UI will expose poor configuration practices that might have been passable in HI, but will break in Open UI. Open UI will also introduce defects as a side effect of the upgrade.

    The simple approach to this problem is to brute force it, and assign a team of developers to navigate to each view to analyse for technical defects. But this isn't really viable, as a long term strategy, a smarter approach is to use web automation.

    Web automation brings to mind images of robots that are used to scrape web sites, harvest email addresses and index content, but Siebel developers have a more important itch to scratch: Testing the Siebel UI.

    The obvious use case is to run continuous integration testing, to ensure that new builds are thoroughly tested over night. A more advanced crawler can be built to perform functional testing of the applications main areas, but the scope of this article is to show how you can build your own Open UI crawler that can be used to programatically navigate to each view, and optionally validate your application.

    Theres no shortage of tools in the market that is available to perform this sort of work, but if we narrow our criteria to open source web automation solutions, the Selenium web driver makes a pretty good choice, as it's also set to become a W3C recommendation. Selenium works with all the major browsers, is compatible with your favorite programming language, and it is also free.

    This article will provide you with a sample application that implements Open UI automation with Ruby, but you can extract the lessons learnt from this article, and implement them in your language of choice.

    I've chosen to use Ruby as my language, because there is solid support for web automation. Ruby has gems that allow the developer to easily parse the DOM, make selections using CSS or XPath syntax, deal with dialog boxes, and take screen shots of problem views. Combining with this Watir, which is a selenium wrapper in Ruby, allows the developer to build the automation quickly in a light weight language.

    The solution for your project may be different, if you require enterprise support, headless servers, or if you plainly prefer to stick to your language of choice, because of your available skill set, then the right tool for your circumstance will be different. The most important ingredient here is Selenium, or in this case Watir, which is a Ruby flavor of Selenium.

    Selenium

    This is the Browser automation API that allows you to control your browser programatically

    http://www.seleniumhq.org/

    " Selenium automates browsers. That's it! What you do with that power is entirely up to you. Primarily, it is for automating web applications for testing purposes, but is certainly not limited to just that. Boring web-based administration tasks can (and should!) also be automated as well."

    Browser Support

    Chrome, Firefox, IE...

    http://www.seleniumhq.org/about/platforms.jsp#browsers

    Remote control drivers

    Javascript, Java, C, Ruby, Perl, PHP...

    http://www.seleniumhq.org/about/platforms.jsp#programming-languages

    Open UI

    The crawler can be run against a thin client, or against a local thick Client running Open UI.

    Ruby Implementation

    Knowing Ruby isn't a prerequisite to understand this concept. Ruby is a pretty readable language, and I've added a good dosage of comments to explain what the code is doing so those from non Ruby backgrounds can follow.

    #import required libraries
    require 'rubygems'
    require 'watir-webdriver'
    require 'cgi'
    
    #Parametise the URL that will be used to navigate to the Open UI application
    #This should really be configured for a server URL for real testing
    #but im using a local URL for this example
    sURL = "http://localhost/start.swe?"
    sPass = nil
    sUser = nil
    
    #Open a new chrome browser session
    browser = Watir::Browser.new :chrome
    
    #navigates to the URL defined above
    browser.goto sURL
    
    #maximise the window so we can see all entire view
    browser.window.maximize
    
    #This block simulates the user login process for thin client connection
    #else it is bypassed for the thick client
    if sUser!=nil && sPass!=nil
         #fill in username in the user name field
         username = browser.text_field(:name, "SWEUserName")
         username.set sUser if username!=nil
         #fill in password in the password field
         password = browser.text_field(:name, "SWEPassword")
         password.set sPass if password!=nil
       
         sleep 1
         #click the login button
         browser.link(:text =>"Login").when_present.click
    end
    sleep 5
    
    

    Congratulations!. At this point we have just created a simple crawler that has logged into an Open UI Application.

    Next we want to tell it how to navigate the application, by going to the sitemap, read all the views, and parse all the JS links embedded in the view elements. This is done pretty easily in Ruby.

         #use CSS to locate sitemap icon, and click it 
         browser.element(:css => "li[name=SiteMap] > img").when_present.click
    
         #use CSS selector to read all the views in sitemap into an array
         #these links also contain an onclick attr that allows to emulate a view navigation
         links =browser.css("span[class='viewName'] > a")
    

    Finally, we loop through every link, execute the JS code to perform the navigation, and optionally perform any automation.

         links.each do |link|
         params = CGI.parse(link.attr("onclick"))
         sleep 5
         #Goto view
         browser.execute_script( link.attr("onclick") )
         end
    

    Thats as simple as it needs to be.

    To fire it up with a dedicated client, you'll first need to open up an existing session to start the local web server. When the above code is run, it will launch a new browser connecting to the existing session.

    For thin client connections, it is not necessary to pre-launch the session, the above code will instantiate a new browser session, and will login in normally.

    A crawler like the program above, can navigate the application, verify each view and flag views that have issues.

    Building a simple crawler is a easy, and is something that you can give to an energetic graduate to perform in a day, however if you require a crawler that can be a workhorse for your Continuous Integration strategy, then it needs to be a little more robust, and more scalable than the simple example above, or if your needs warrant a more specialized crawler, that can check for and enforce WCAG compliance in your application, then you'll ideally need a plugin system that can allow you to easily add new validators. If want to go a little further, and build in functional testing capabilities, then could build an API bridge to facilitate a DSL to build more readable test cases.

    Open UI + selenium opens up these exciting opportunities. For my client, having a nightly build process + continuous integration, and WCAG reporting, ensures they have a strict standards compliant UI and a more stable application for every deploy.

    This article provides the necessary ingredients, and a simple recipe for other Siebel customers to follow the same path.

    Further Reading

    Selenium Web driver
    Continuous Integration
    WCAG

    More on Open UI

    Serious Open UI developers should be on the watch, for the up comming Open UI book from some very distinguished authors

    Siebel Open UI Developer's Handbook

    Open UI: Grunt and JSHint

    Previously we looked at how Grunt can improve your Open UI development experience, by automatically reloading the browser, every time a source file is changed. This time we take another step, and configure an automated code check using Grunt and JSHint.

    Think of how Siebel Tools prevents you from compiling, or checking in poorly constructed eScript. JSHint will do the same (if not better) job of making sure your client side code is free from language errors, and potential problems.


    Starting from where we left off in Open UI: Grunt and Livereload, we should have an operational grunt job that reloads our browser. To add JSHint to the mix, we need to modify the package.json file slightly, to include one new line to install this new dependency

    {
      "name": "SiebelGrunt",
      "version": "0.0.1",
      "devDependencies": {
        "grunt": "~0.4.1",
        "grunt-contrib-watch": "^0.6.1",
        "grunt-contrib-jshint": "^0.10.0"
      }
    }


    With your command prompt at the directory with this package.json file, type the following command to pull down this module

    npm install

    Now we need to configure the Grunt build process, to register this new task, and configure it so it only checks changed files.

    module.exports = function(grunt) {
         var sJSPath = '../PUBLIC/enu/*/SCRIPTS/**/*.js';
        grunt.initConfig({
            pkg: grunt.file.readJSON('package.json'),      
              jshint: {
                   scripts: [sJSPath]
              },

              watch: {
                   scripts: {
                        files: [sJSPath],
                        tasks: ['jshint'],

                        options: {
                             nospawn: true,
                             livereload: true
                        },
                   }
              }
        });
        grunt.loadNpmTasks('grunt-contrib-watch');
        grunt.loadNpmTasks('grunt-contrib-jshint');

         grunt.event.on('watch', function(action, filepath){
              grunt.config(['jshint', 'scripts'], filepath);
         });

         grunt.registerTask('default', ['watch']);
    };

    To explain what I've done, this newly added block registers JSHint, and uses the file path that we defined previously
              jshint: {
                   scripts: [sJSPath]
              },

    Next, I register the 'jshint' task with the 'watch' task, which will run JSHint every time a file is changed.
                        tasks: ['jshint'],

    The only problem with this configuration is that, JSHint has no idea which file was modified under the watch task, so JSHint will run over all 1500+ files under Siebel scripts and lint them all. If this is run without a filter, your command prompt will hang, and after a while it will come back with a window similar to this.



    JSHint reports 15K plus errors across all the files under the Siebel scripts folder, but its not all bad, some of the code under Siebel/scripts is 3rd Party which can raise some errors, and the rest will have been through Oracle's own lint process, and then minified before delivery to customers. We should only be concerned with the files that we have control over.

    To make JSHint validate a single file, I added the following block.
         grunt.event.on('watch', function(action, filepath){
              grunt.config(['jshint', 'scripts'], filepath);
         });

    This configures a listener on the watch event, and passes in the name of the modified file to JSHint. Now we are ready to roll. Fire up the grunt process.

    grunt

    Switch back to your code editor, open up postload.js, and type in every body's favorite command "asdf"


    Watch will automatically fire JSHint, and report back on the errors, with the line number and a description of the problem. Note that we lost our purple background because our script failed to parse.

    Alright, now lets fix that bug


    We see that JSHint has reported "1 file lint free", and our browser has automatically reloaded in the background with our purple background again. 

    JSHint won't make your Siebel developers better web developers. It will only ensure that any code that is written is written with a consistent syntactic style, and that critical language errors are picked during build and unit test, but it will provide you with the coordinates of any errors that is picked up. 

    JSHint is a great safety net, but remember its recommendations can be ignored. To enforce this on a broader scale JSHint can be configured to run against the entire code base on a regular basis, and its results can be enforced by a QA process.

    In the case above, a small typo caused 12 errors resulting in missing background color, but in other scenarios, these kinds of errors could cause more serious issues, which illustrates the importance of a code quality tool like JSHint.

    Hopefully this article has piqued your interest in Client side build automation. Are you using similar tools on your project, or have you adapted a build process that you would like to share with the Siebel community?.

    Send in your comments.

    Open UI: Grunt with Livereload

    With Siebel Open UI development we naturally spend the majority of our time working on the browser. Part of that requires clearing the browser cache, and hitting F5 to reload the browser.

    Sometimes we reload the browser, and think afterwards, did I really clear the cache that time? Maybe not... so we the clear cache, and reload the browser again. Seconds later, we spot a typo in our code and the endless cycle continues. Wouldn't it be good if we can focus on writing the code, and let something else clear the cache and reload the browser? 

    This is where tools like Grunt can help.

    1. Download No Cache

    "No Cache" is a Chrome extension that I wrote to automatically clear your browsers cache. It is available for free on the chrome store. There are 1 click solutions out there, but I prefer to have an automatic background process take care of this.

    1. Right click on the No Cache icon, and choose "Options"
    2. On the "Cache" tab select "Enable for filtered requests"
    3. On the "Filters" tab select the input box, type in "start.swe" and click the + icon


    This tells No Cache to clear the cache for every request Siebel makes, this should work on the local or thin client. If you use another browser and have your own favorite clear cache plug in, then that will work as well.

    2. Install Grunt

    Grunt is a JavaScript task runner that we can configure to reload our browser, but first we need to download and install Node.js from this website


    Its not necessary to know what Node.js does, just know that it provides us with the environment to run Grunt. Once you've installed Node.js open a command prompt and type the following line to install the grunt command line globally.

    npm install -g grunt-cli

    Next we need to decide where to put our Grunt configuration. I like to put it under the Siebel/Client folder because I backup the PUBLIC folder, and don't want copies of Grunt and its modules copied along with this backup.

    Open a command prompt, cd to your Client folder, and follow the next set of instructions to install and configure grunt on your local machine.

    mkdir grunt
    cd grunt

    Create a new file in the same folder called package.json, with the following contents

    {
      "name": "SiebelGrunt",
      "version": "0.0.1",
      "devDependencies": {
        "grunt": "~0.4.1",
        "grunt-contrib-watch": "^0.6.1"
      }
    }

    back at the command prompt, type the following command to download the required dependencies defined in the file above

    npm install

    Next create a new file called Gruntfile.js, with the following contents

    module.exports = function(grunt) {
         var sJSPath = '../PUBLIC/enu/*/SCRIPTS/**/*.js';
        grunt.initConfig({
            pkg: grunt.file.readJSON('package.json'),
              watch: {
                   scripts: {
                        files: [sJSPath],
                        options: {
                             livereload: true
                        },
                   }
              }
        });
        grunt.loadNpmTasks('grunt-contrib-watch');
         grunt.registerTask('default', ['watch']);
    };

    This file is important, so lets run through it line by line

    >     var sJSPath = '../PUBLIC/enu/*/SCRIPTS/siebel/custom/**/*.js';

    This path specifies the folder and file pattern that Grunt will watch for changes. Essentially we want to watch for any changes to JS files in the Siebel script folders

    > watch: {

    This line defines a "watch" task, there are other tasks that can be run. Eg code lint/minification/, but I've kept it simple for this example.

    > files: [sJSPath],

    configure the files to watch using the path statement above

    > livereload: true

    This option will spawn a background process that will cause your browser it refresh automatically. To Integrate our browser with this background process, there are 3 options as described here


    The browser extension is the most suitable method for development, so go ahead and download it with these instructions.

    To start it all up, go back to the command prompt and simply type
    grunt

    To test it out, goto SCRIPTS, open up any JS file like "postload.js", modify it as below, and watch your browser reload magically.



    I've presented the bare minimum required to get up and running with Grunt, but once you have that setup, you no doubt want to look into what other things Grunt can do for you.

    A good place to start is..




    Open UI: Close Popups on Escape Key

    The ESC key in many traditional applications, provides a convenience shortcut to stop, undo, or go back in the application. It enhances the usability of an application by allowing the user to navigate backwards quickly or work more effectively work with two hands.

    This feature is also built into major modern browsers, with the ESC key allowing users to dismiss dialog boxes. This feature works on native browser dialogs such as the alert, confirm and prompt dialogs, as well as artificial dialogs, such as the new notification pane seen in Open UI.



    Right click on the Notification Pane, and inspect the source code behind this UI element. It will look something like this:



    The class attribute of the container gives it away as a jQuery dialog.

    You may not know it, but you will be interacting with jQuery dialog boxes throughout Siebel Open UI. Just inspect the browser source for any MVG, Pick or any Popup applet in Open UI, and it will reveal the same class attributes as the Notification Pane. This tells us they are constructed using the same plugin, except these other 'dialogs' cannot be dismissed with the ESC key.

    Lets see if we can imbue these other applets, with the ability for us to dismiss them using the ESC key.

    This behaviour is controlled by the following code snippet

    $("[name=popup]").dialog({
        position : "center",
        modal : true,
        autoOpen : false,
       //This line disables the ability to dismiss dialogs using ESC             
       closeOnEscape : false
    });
    
    


    Log onto the Application, and select “Help > About View”, open the Query Assistant Applet, or choose any other popup applet, this will initialise the jQuery dialog. Press the ESC button, and at the moment, nothing will happen.

    Press F12 on your browser to bring up the built-in debugger, and paste the code below.

            $("[name=popup]").dialog( "option", "closeOnEscape", true );
    


    Now with that same popup applet still open, press ESC, and watch that applet disappear. Any popup applet that is opened in the Application at this point also inherits this behaviour.

    The above code will act the basis of our solution, but it only works if a popup has already been opened, so the most appropriate event is the first time a popup applet is displayed. This makes ShowUI a good candidate, as we can detect when a popup is open, and set the dialog option.

    For testing purposes you can put the following code the ShowUI handler of any popup renderer.

    if (!$( "[name=popup]" ).dialog( "option", "closeOnEscape" ) ){
        $("[name=popup]").dialog( "option", "closeOnEscape", true );
    }
    


    The above code detects if the closeOnEscape option of the jQuery dialog is disabled, and proceeds to enable it. It works even if the first popup applet is opened, and has been initialised to false.

    The above code only works for a particular applet, and any subsequent popup applets opened from that point onwards, so a better approach is to put the code under the global ShowUI handler, which will enable the feature for any popup that is opened, however we only want to run it once.

    To achieve this, we can use the technique below, which re-writes the logic to cut out the if statement after the first time the dialog option is set.

    Note: This code is provided for educational purposes only, and should not be used in production environments.
    function Global_PreShowUI(){
        //Call the usual ShowUI function
        Global_PreShowUI_Rewrite();
    
        //Perform popup dialog check
        if (!$( "[name=popup]" ).dialog( "option", "closeOnEscape" ) ){
            $("[name=popup]").dialog( "option", "closeOnEscape", true );
            //Rewrite the current function after the first popup has been detected
            Global_PreShowUI=Global_PreShowUI_Rewrite;
        }
    }
    
    
    function Global_PreShowUI_Rewrite(){
        //Show UI code goes here
    }
    


    The line below shows where the application logic is being re-written.

        Global_PreShowUI=Global_PreShowUI_Rewrite;
    


    The subsequent time Global_PreShowUI is fired, it will go straight to the real Global_PreShowUI_Real handler, which means our conditional test is only evaluated once. Neat!

    Usability has always been thrown in the too hard basket, and although browser script techniques have been available to improve usability in Siebel HI, it has only been attempted by the brave.

    The ESC key is already used in Siebel to undo query, undo record, and dismiss dialogs. Open UI now provides the option for customers to extend this feature to all Siebel popup applets.

    Open UI: SiebelJS.Log & The Stack

    Introduction

    SiebelJS.Log allows the developer to debug the application by writing to the browser console. This has been provided since 8.1.1.9.

    Most modern browsers have console.log which performs the same function, except console.log causes an error in IE if the developer toolbar has not already been opened. SiebelJS.Log avoids this problem by first checking if the console object is available, before calling the log method.

    SiebelJS.Log is better for this reason.

    SiebelJS.Log in 8.1.1.11

    In 8.1.1.11 Oracle changed the way SiebelJS.Log works.

    SiebelJS.Log no longer supports a string argument, but rather an Error object. This also means all previous log statements from 8.1.1.10 will be ignored silently.

    Update: The above behaviour affects the only the 8.1.1.11 sample application. SiebelJS.Log has been redefined in 8.1.1.11 to support an Error object, but passing in a String will still log to the console. Usage
    TestPRList.prototype.ShowUI = function(){
        hello_a();
    };
    
    function hello_a(){
        hello_b()
    };
    function hello_b(){
        hello_c()
    };
    function hello_c(){
        SiebelJS.Log( new Error('Error occurred here')) ;
    };
    


    Result


    Notice that it produces an error message with a stack trace.

    If you don't like the stack trace produced by SiebelJS.Log, you can call SiebelJS.Trace or console.trace directly to produce a stack trace without the error message.

    Just substitute SiebeJS.Log with console.trace in the above example. This results in a much cleaner output as can be seen below.



    console.trace is supported in Chrome, Firefox, and Opera.

    Conclusion

    Although there has been no official documentation page on the SiebelJS class and its available methods, it has been used in Oracle Bookshelf alongside usage examples of other methods, and as such, can be considered to be implicitly documented.

    For better or for worse SiebelJS.Log has been redefined to be used as an error logger. Open UI customers that need a method for logging, are better off creating a wrapper around console.log, and put it in a client side library (if you haven't already done so).

    8.1.1.11 brings many changes, including unspecified changes to the browser API. Customers that are upgrading from 8.1.1.10, will find that customizations don't port over without some rework.

    The temptation to use undocumented methods is ever present, because the source code is more readable than the documentation. This serves as a good reminder for us to avoid using undocumented methods, or at least have a good mitigation strategy if you do use them.

    Open UI: Build Process


    Introduction


    Siebel projects that are crossing over to Open UI, will soon realise the immediate need to implement standards around the development and deployment of the external resources in Open UI. Unlike traditional Siebel development, where source code is checked into a database and compiled into a SRF with a proprietary IDE, Open UI is based on text files that can be edited using Notepad.

    Managing external files is not new to Siebel development, but it has never been mainstream, and the processes for managing these resources are not as mature on Siebel projects. This article is aimed at Siebel development managers looking to incorporate best practice from file based development environments, and integrating them with Open UI.

    Traditional Vs Modern Siebel Development

    A traditional Siebel development cycle involves the developer checking out and locking a project/object, the developer performs the modification, and checks it back in. The Siebel repository stores the source code, which must be compiled into SRF.

    Parallel development can be achieved by following the above process with a second repository, and managing the code promotion through a separate environment path. Eventually when those code bases needs to be merged, objects are moved between repositories through exporting and importing SIFs. Siebel provides a decent merge tool for configuration changes, but merging script is always a risky affair, due to the inability to perform a line by line merge.

    With Open UI development, developers have to switch from working with a database base repository to a file based repository. This file based repository is similiar to the Siebel repository that it stores the single source of truth for a particular release. Parallel projects can also be supported in a similiar manner, by establishing a separate container.

    Open UI development is very much like web development, files can be edited freely in your code editor of choice, the application can be easily debugged from any browser, and you have the flexibility to pick and choose your add-on web technologies. There is no longer a dependency on the Siebel way to build your application.

    Building your Application

    Do you use XSLTs, client side business services, custom images/JS/CSS, customized web templates? Then you need a build process. A build process allows projects to automate the production of artefacts for deployment with the following advantages

    * Resources are deployed in a consistent manner
    * Automated quality checks
    * Files are optimised for Production
    * Integration with Source Control
    * Automated unit testing

    To achieve the above tasks, we’ll need to establish the build process, procure the required toolset, and develop the necessary automation.

    Establishing a Process

    Projects need a formal strategy to manage the migration of files from source to deployment. Fundamentally this process should address how files are tracked, changed, merged and deployed, across multiple parallel development streams. This shouldn’t be new territory, so projects don’t have to go shopping for a new process, unless it is broken. Existing HI file deployment processes can be carried over, and augmented to incorporate the some of the best practices from modern web development.

    * Integrated Source control
    * Continuous Integration
    * Automated test cases

    SCM (Source Control)

    The concept of building an application requires that we have a source. For traditional Siebel development, this source is the Siebel repository, in Open UI, the source should also be in a repository, but it is more likely to be a file repository rather than a database repository.

    There are many solutions in the market that offer Software Configuration Management (SCM), but it is up to each project to choose the appropriate solution for their needs. The most obvious place to look is within your own organization to discover what SCM software is available, and used by other IT sections. This allows you to take advantage of the licensing agreements, and leverage the existing support arrangements that are already in place. Open source/free SCM sounds great, but it is risky to introduce it into an Enterprise, unless you have the expertise to maintain/support it yourself, otherwise prepare to negotiate for the support arrangements.

    Build Automation

    Automation tools allow projects to enforce a standard build process to a set of source files, create build packages and deploy it into its destination. Many projects schedule the automatic compilation of the SRF, generating browser scripts, and deployment of the SRF into development environments overnight. The same process could also be extended to deploy Open UI resources into the same development environment.

    Here are some examples of automation that can be applied to Open UI resources before deployment

    * Add standard headers such as revision, organization details, release information, disclaimers to all files
    * Strip comments from files
    * Compress JS/CSS files through minification
    * QA JS files with JSHint/JSLint
    * Consolidate common JS Files to reduce HTTP requests
    * Extract metadata from JS files, and produce documentation
    * Run unit test scripts

    Are you using a build process for HI? The same principles can be used in HI for other external resources.

    Here are some ideas

    * Automatically turn off XSLT indenting
    * Generating XSLT documentation
    * Extracting eScript from the repository and validating it
    * Run interface test cases

    Code Editors

    While Notepad and other standalone editors are sufficient, it is recommended that you choose an editor that has the capability to detect JS language errors, integrate with your SCM, and automation tools.

    Siebel Tools has this capability built in to an extent, but in Open UI, if you miss a closing bracket, and it is loaded via a common file, your application could fail silently, and pin pointing it to its exact source might not be fun. Code editors can perform the role of QA, by ensuring that your code is free from syntax errors before it is run against more stringent QA checks by programs like JSHint/JSLint

    Automated Test Cases

    Automated test cases are used as part of Continuous Integration to ensure that changes from multiple developers, work coherently and doesn’t break the application. The challenge is that developing automated test cases for the UI isn’t easy, at least not for traditional HI UIs. Open UI promises to be open, and exposes a new set of APIs for browser automation tools to interact with.

    Until Oracle provides an Open UI automation client (for developers), conceptually, a project could develop their own Open UI automation testing, using a combination of the following tools

    * Selenium
    * Watir
    * Ruby
    * Custom JS OUI automation API

    Selenium provides the browser automation, Watir is a ruby library that provides a nice abstraction onto of Selenium, and a custom JS interface provides the API bridge between Ruby, and the Open UI client.

    Toolchain

    Your build process will be dependent on the tools that’s supported by your organisation. The best tools for the job, are dependent on a variety of factors, like cost, availability, familiarity, features, and support. There isn’t a standard set of tools for everyone, however the main tools in your arsenal should be the IDE, SCM, and Automation tools

    * List of source code editors
    * List of build automation software
    * List of SCM software

    Other tools to investigate

    * JS minification
    * JS/CSS consolidation
    * JSHint/JSLint
    * JS Documentation

    Conclusion

    Establishing a build process isn’t something that is restricted to large teams, it just important to have this process in place for a single developer working offline. There are times when you need to blow away an environment and rebuild from source control, or you need to rebase to a completely new project. Having source code control becomes critical for working in Open UI especially in large teams, so gone are the days where it is acceptable for developers to modify files directly on the web server.

    A build process can ensure that your application remains stable throughout the iterative development process, or alternatively without one, your project can become a cowboy arena, where developers go in, make adhoc changes, send off different file revisions for deployment via emails, and break the application at every deploy. The choice is pretty clear.

    Further reading

    * Why use source control
    * Setting up a JS Build Process
    * Siebel Continuous Integration Setup
    * Branching, Merging concepts with Git

    Some customers will have more mature processes than others, so what build tools are you using in your project?

    To kick this off, here’s what works for me

    * ANT (Build automation)
    * ClearCase (SCM)
    * Eclipse (IDE) + ClearCase Plugin
    * Sublime (IDE)
    * Eclipse JSHint integration via RhinoJS
    * YUI Compressor (JS minification)
    * + Selenium & Ruby for some UI automation fun

    Open UI: Tab Order Defect

    In a scene from The Matrix I, Neo has a moment of deja vu, when he sees a black cat walk by twice. His companions freeze in their tracks, because this usually means The Matrix has been altered. Readers of Impossible Siebel, also had their black cat moment, when they discovered the Tab Order defect in 8.1.1.1, and then saw the same Tab Order defect again in 8.1.1.2. Just like that deja-vu moment, here we are again facing a Tab order defect in Open UI popup applets.

    This problem affects any popup applet in OUI that has a custom tab sequence. To see this problem visually, goto any applet, enter query mode, and click on the Query Assistant applet.

    Right click on the first control this applet, and inspect the source for this element.



    Do the same for other controls and buttons on this applet. By default, all controls on a popup applet have a tab index of 0.



    Next, find this applet in Tools, and add a custom Tab sequence, the actual sequence doesn't matter, as long as it is defined. This causes Siebel to produce the tabIndex attribute, and provide an actual sequence.

    After our little modification, inspect the source code again, and it should look something like this.



    This looks good, Siebel correctly generates the correct tab sequence, however try tabbing through all the controls on the applet, and watch what happens when the the last tab sequence is reached



    The tab order doesn't cycle back to the first control as it did before, but it jumps back to the base applet behind the popup applet. This causes a commit, and can produce the above error, depending on what your base applet is.

    The reason behind this is, Siebel provides each of the base applets with a starting range for the tab sequence. The first applet starts at 1000, the next starts at 2000, and so on. When a popup is invoked, the controls on that applet are 0, if no tab sequence is specified, when a tab sequence is applied Siebel produces a tab sequence starting from 10000. When the last control is reached on the popup applet, it naturally returns back to the first applet, causing a commit and consequently an error.

    Solution

    1. Create a global render
    2. Detect if the applet is a popup
    3. Check for a custom tabIndex sequence
    4. Put a custom event handler on the last tab index
    5. Move the cursor back to the first control, when it reaches the last control

    Steps

    The strategy to create a global handler has already been discussed in the Open UI: Mouseover Tool Tips - Part 1 article.

    1. In summary create the following JS file

    //GlobalShowUI.js
    if(typeof( SiebelAppFacade.PhysicalRenderer.prototype.bShowUIProxy ) === "undefined"){
        SiebelAppFacade.PhysicalRenderer.prototype.bShowUIProxy=true;
        SiebelAppFacade.PhysicalRenderer.prototype.ShowUI=(function(){
            var PRShowUI = SiebelAppFacade.PhysicalRenderer.prototype.ShowUI;
            return function(){
                Global_PreShowUI.apply(this, arguments);
                PRShowUI.apply(this, arguments);
                Global_PostShowUI.apply(this, arguments);
            };
        }()); 
    }
    
    
    //put Global Pre ShowUI logic here
    function Global_PreShowUI(){
    var pm=this.GetPM();
         if($("div[name='popup']").length>0){
               SiebelApp.ABC.fixPopupFormAppletTabOrder(pm);
         }
    }
    
    
    //put Global Post ShowUI logic here
    function Global_PostShowUI(){
    }

    2. Add an entry into the custom_manifest.xml

    3. Add the following code sample into your client side script library.

    SiebelApp.ABC.fixPopupFormAppletTabOrder=function (pm){ 
        /*************************************
         SAMPLE ONLY, NOT FOR PRODUCTION USE!
        *************************************/
        var oAppletTabIndex,aAppletTabIndex; 
        //Get all popup applet controls with tabindex
        oAppletTabIndex = $("div[name='popup']").find("div.mceGridField>input[tabindex], button[tabindex]");
        if(oAppletTabIndex.length>0){
            //turn jquery object into array for sorting
            aAppletTabIndex = jQuery.map (oAppletTabIndex, function( o) {
                return ($(o).attr("tabindex")>0)?{ tag: $(o).prop("tagName"), seq: $(o).attr("tabindex"), sel: ($(o).prop("tagName")=="BUTTON")?"[id='"+$(o).attr("id")+"_Ctrl']":"[name='"+$(o).attr("name")+"']" }:null;
            });       
    
            //sort on seq
            aAppletTabIndex.sort(function(o1, o2) { return o1.seq > o2.seq ? 1 : o1.seq < o2.seq ? -1 : 0; });
    
            if(aAppletTabIndex.length>0){                     
                var sSelectorFirst=aAppletTabIndex[0].sel;
                var sSelectorLast=aAppletTabIndex[aAppletTabIndex.length-1].sel;
                //attach jquery handler on last tab index, and set focus back to the first tab index
                $(sSelectorLast).blur(function() {
                    $(sSelectorFirst).focus();
                });
                $(sSelectorFirst).focus();
            }
        } 
    
        /*************************************
         SAMPLE ONLY, NOT FOR PRODUCTION USE!
        *************************************/
    
    }
    


    Code Explanation

        
        //Get all popup applet controls with tabindex
        oAppletTabIndex = $("div[name='popup']").find("div.mceGridField>input[tabindex], button[tabindex]");
    }
    
    Grabs all fields and buttons on the popup applet where there is a tab index defined.

            
        //turn jquery object into array for sorting
        aAppletTabIndex = jQuery.map (oAppletTabIndex, function( o) {
                return ($(o).attr("tabindex")>0)?{ tag: $(o).prop("tagName"), seq: $(o).attr("tabindex"), sel: ($(o).prop("tagName")=="BUTTON")?"[id='"+$(o).attr("id")+"_Ctrl']":"[name='"+$(o).attr("name")+"']" }:null;
            });    
    }
    
    The above command uses jQuery.map, to turn the first jquery object into a filtered array, of objects that contains the control selector, and tab sequence.

                
             //sort on seq
            aAppletTabIndex.sort(function(o1, o2) { return o1.seq > o2.seq ? 1 : o1.seq < o2.seq ? -1 : 0; });
    
    Apply a sort on the new array, based on the sequence property that we created using the jQuery.map command.

                
                var sSelectorFirst=aAppletTabIndex[0].sel;
                var sSelectorLast=aAppletTabIndex[aAppletTabIndex.length-1].sel;
                //attach jquery handler on last tab index, and set focus back to the first tab index
                $(sSelectorLast).blur(function() {
                    $(sSelectorFirst).focus();
                });
    
    
    Once we have a sorted array, we can use the selector attribute to attach a handler to the last tab index control, which forces focus back to the first control

    4. Restart your client, and now enjoy your new found love for tabIndex

    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.