Monday, January 14, 2013

Siebel Open UI Extensibility Part 2: The Google Maps Example (Updated)

Welcome to part 2 of our small series on Open UI scripting. In the previous article, we have discovered the scaffolding for a presentation model. Today, we will talk about the physical renderer file and we will see that they are quite similar in nature.

Instead of boring you to death with another boilerplate, I thought it would be worthwhile to apply one of the most prominent requirements to Siebel Open UI...

Of course, we are talking about displaying an account's primary address in a (Google) map. Siebel Essentials veterans might guess where this is going ;-)

During the early phase of Open UI development, I had access to some beta systems which sported a map icon next to the Postal Code field on the account form applet. On hovering or clicking the icon, the map was displayed in a floating window.

Later, this became one of the practice examples for teaching partners and internal employees how to configure Open UI. I took this example and expanded it a bit. The example also shows how to integrate third party libraries.

So here is what we want to achieve:

Google map in Siebel Open UI. Click to enlarge.
And here is the cookbook:
  1. Create the presentation model
  2. Create the physical renderer
  3. Update the extension mapping file
  4. Add third party libraries (optional)
  5. Update the custom manifest file
  6. Add custom styles (optional)
  7. Test
And here we go:

1. Create the Presentation Model

The general structure of a presentation model file has been laid out in the previous post. By obeying those rules, we create the necessary code to implement a new class named ALEXMapHoverPM. So the first part of the file will look like this:


(Don't worry, I uploaded all files to Google Drive, so you can get them)

In the presentation model class, we add the stPostalCodeHTML property which will hold the replacement HTML code for the Postal Code control.

this.AddProperty("stPostalCodeHTML");

Additionally we add the following methods:

//add two standard methods "ShowSelection" and "FieldChange"
this.AddMethod("ShowSelection",SelectionChange,{sequence:false, scope:this});
this.AddMethod("FieldChange",OnFieldChange,{sequence:false, scope:this});

//add a custom method createMapHTML; must use this.ExecuteMethod to call!
this.AddMethod("createMapHTML",createMapHTML,{sequence:false, scope:this});

Note that we add a custom method named createMapHTML which will implement the HTML code generation. We also add the two standard methods ShowSelection and FieldChange to be able to re-run our code every time a new record is selected or a field value is changed.

The code for the two standard methods is quite simplistic:

function SelectionChange()
{
     //execute the createMapHTML function
     var stPostalCodeHTML = this.ExecuteMethod("createMapHTML");
            
     //set the stPostalCodeHTML property
     this.SetProperty("stPostalCodeHTML",stPostalCodeHTML);
}
function OnFieldChange(control,value)
{
    if (control.GetName() === "PostalCode")
    {
        var stPostalCodeHTML = this.ExecuteMethod("createMapHTML");
        this.SetProperty("stPostalCodeHTML",stPostalCodeHTML);
    }
}

As we can see, the SelectionChange function executes the createMapHTML method to populate the stPostalCodeHTML property. So does the OnFieldChange function, but only when the PostalCode control is the one which undergoes a value change.

And here is the createMapHTML function in all its glory:

function createMapHTML ()
{
    //the map URL goes here (note 'output=embed' for Google Maps)
    //good place to try other map providers like Bing, Nokia or Oracle
    var mapURL = "http://maps.google.com/maps?z=14&ie=UTF8&hl=en&output=embed&q=";

    //get a handle on the applet controls
    var controls = this.Get("GetControls");

    //get the address field values and concatenate
    var ctrlStreetAddress = controls["StreetAddress"];
    var ctrlPostalCode = controls["PostalCode"];
    var ctrlCity = controls["City"];
    var ctrlCountry = controls["Country"];
    var stStreetAddressValue = this.ExecuteMethod("GetFieldValue",ctrlStreetAddress);
    var stPostalCodeValue = this.ExecuteMethod("GetFieldValue",ctrlPostalCode);
    var stCityValue = this.ExecuteMethod("GetFieldValue",ctrlCity);
    var stCountryValue = this.ExecuteMethod("GetFieldValue",ctrlCountry);
    var stFullAddress = stStreetAddressValue + "," + stPostalCodeValue + "," + stCityValue + "," + stCountryValue;

    //get the element ID of the postal code field
    var fieldElemId = ctrlPostalCode.GetInputName();

    //build the HTML for the postal code filed
    var stPostalCodeHTML = '<input type="text" name="' + fieldElemId + '" value="' + stPostalCodeValue + '" style="float:left;width:105px;height:24px"></input><a class="iframe" href="' + mapURL + stFullAddress + '"><img name="myMapsIcon" src="images/map.gif" height="16" width="16" style="float:right;"></img></a>';
        
      return stPostalCodeHTML
}

The function basically does the following:
  1. Define the base URL for the map provider (Google maps in the example).
  2. Use the GetControls property to get an array of all current applet controls.
  3. Access the address controls (must use the Control names defined in the repository).
  4. Use the GetFieldValue method to get the address control values.
  5. Concatenate the address data into a comma separated string.
  6. Use the GetInputName method to get the DOM element Id of the postal code control.
  7. Generate the replacement HTML for the postal code field by concatenating HTML with variable values. The resulting HTML is a textbox with the postal code (which is the original control) plus a map icon which is wrapped in a hyperlink (class="iframe") to the full map URL.
As the code is for educational purposes, it is quite verbose and will likely be different in your implementation.

If you use any custom icon, you must of course upload it to the PUBLIC/#language#/IMAGES directory.


We must save any custom JavaScript file for Open UI in the following folder location:

PUBLIC\#language#\#build#\SCRIPTS\siebel\custom

For the example, I saved the file as alexmaphoverpm.js.

2. Create the Physical Renderer

The physical renderer class is contained in a separate file and is responsible for manipulating the DOM so that the desired UI behaviour is achieved. The basic skeleton of a physical renderer file is strikingly similar to a presentation model. Below is a screenshot of the scaffolding. You may want to download the full example file here.


The main difference is that a physical renderer will use the GetPM method to access the presentation model framework and one of the first methods to be used is the AttachPMBinding method. The purpose of this method is to create a binding between a physical renderer function and a property. In our example we bind the stPostalCodeHTML property to the MapHover function. This causes the MapHover function to be executed each time the stPostalCodeHTML property is changed by the presentation model.

In addition, we use the SiebelJS.Extend method to inherit the base functionality of the pre-built PhysicalRenderer class.

After providing the base scaffolding, we can write the custom MapHover function:

function MapHover()
{
    //get the controls
    var controls = this.GetPM().Get("GetControls");
    var ctrlPostalCode = controls["PostalCode"];
    var stPostalCode = ctrlPostalCode.GetInputName();
    var stPostalCodeHTML = this.GetPM().Get("stPostalCodeHTML");

    //jQuery magic...
    $('input[name="' + stPostalCode + '"]').parent().html(stPostalCodeHTML);
    
    //colorbox magic...
    $(".iframe").colorbox({iframe:true,width:"65%",height:"75%",opacity:0.5});
}

The function does the following:
  1. Use the presentation model's (GetPM) Get method to read the GetControls property.
  2. Get the DOM element Id of the postal code control.
  3. Get the value of the stPostalCodeHTML property.
  4. Use the $() method of the jQuery library to manipulate the postal code control so that the original HTML is replaced with the generated HTML code. (jQuery comes with Open UI so we can use the full jQuery API).
  5. Optionally, play with third party or custom libraries (colorbox in the example) to accomplish a slick and lean appearance.

Again, we must save the file in the following folder location:

PUBLIC\#language#\#build#\SCRIPTS\siebel\custom

For the example, I saved the file as alexmaphoverpr.js.

3. Update the Extension Mapping File

Now we must associate the applet(s) with the extended presentation model and physical renderer files. We have two choices to do so:

  1. Modifying the manifest_extensions.map file in the OBJECTS folder
  2. Adding user properties in the Siebel Repository

The recommended (as per the Configuring Open UI guide) approach is to modify the file named manifest_extensions.map. This file is located in the OBJECTS folder of the Siebel Server or Developer Web Client. The file contains one section for presentation model mappings and one for physical renderer mappings. The syntax within the sections is Applet or View = Key, where Key must match exactly the strings we use in the RegisterConstructorAgainstKey functions in our code.

Update: As a dear commenter pointed out some problems with this extension, I re-tested against a fresh install and encountered some strange behavior of the manifest_extensions.map file.

We would expect that a valid manifest_extensions.map file would look as follows:


But on some occasions this might not work as expected. If this is the case, add an empty [Presentation_Model] section to the beginning of the file so that it looks similar to the following:


Strange enough, but this rectifies the problems. (End of update)

The main benefit of using the bespoke approach is that we gain complete independence from the Siebel Repository and do not have to compile or deploy the SRF file.

The second (alternative) way to "tell" the applet which presentation model and physical renderer to use is by setting the Physical_Renderer and Presentation_Model applet user properties (which are of course only available at patch level 8.1.1.9 or 8.2.2.2 and higher).

The values of the user properties must match exactly the strings we use in the RegisterConstructorAgainstKey functions. So the correct values for our example would be as follows:


As we can observe, the example is built on top of the SIS Account Entry Applet which is the default applet in the SIA Account list and detail views.

Of course, we must compile the applet after the change and distribute the SRF file when we chose this approach.

4. Add Third Party Libaries (optional)

If you used third party (or custom) libaries which are not yet part of the framework, you must add them to the following location:

PUBLIC\#language#\#build#\SCRIPTS\3rdParty

In the example, we use the colorbox libary so we must extract the downloaded library files to the colorbox subdirectory.

5. Update the Custom Manifest File

Siebel Open UI uses a set of XML files to link the names we use in the applet user properties to the physical files which store the JavaScript. These files are found in the OBJECTS directory and are named custom_manifest.xml and core_manifest.xml. The custom_manifest.xml file must be modified for any custom presentation model or phyiscal renderer.

So to be in tune for our example, we open the custom_manifest.xml file in the OBJECTS directory (making a backup copy before is a good idea) and add a new <KEY> element to the <KEY_COMMON> parent element. After the change, the file looks like this:


The Name attribute of the KEY element must match exactly the value of the Presentation_Model key name we defined earlier.

Note that the KEY element contains FILE_NAME elements which reference the custom, third-party and base files involved in the implementation. The base files applet.js and pmodel.js include the pre-defined presentation model methods we use in our code, so we must include them.

Next, we find the <PLATFORM_KEY_SPECIFIC><PLATFORM Name="Desktop"> section and add a new KEY element with the necessary FILE_NAME child elements to represent the files involved in the custom physical renderer.


The Name attribute must now of course match exactly the value of the Physical_Renderer key name we defined earlier.

The FILE_NAME child elements reference the custom, third-party and base files involved. The phyrenderer.js file is the out-of-the-box definition of the physical renderer and must always be included.

6. Add Custom Styles (optional)

If we use custom CSS styling, we must edit the theme.js and themetree.js (Note: themetree.js might not exist in your build, so just stick to theme.js) files (located in the SCRIPTS/siebel folder and add a reference to the custom or third-party style sheet files to the css section of all defined themes. After the modification, the files look similar to the following:


In the example, we add a new line named "colorbox" (we use this name as a reference in the physical renderer file) to every theme defined in the files. The value of the entry is a reference to the .css file which contains the styles we want to use in the implementation.

7. Test

Before we can test, we must ensure that all repository changes (usually only applet user properties) are compiled and the SRF is distributed. In addition, we must clear the cache of our browser and then launch the client.

If all goes well, we should now see a map icon appear next to the Postal Code control in the account form applet. Hovering over the icon should allow us to see the URL in the browser status bar.


And finally clicking on the icon should open a "colorboxed" frame displaying the map:


Summary

The above example, together with the previous post, demonstrates the basic steps to extend the Siebel CRM Open UI framework with custom UI-based functionality.

We must of course bear in mind that displaying a map (as shown in the example) is also possible in a classic or Open UI client (as shown here) using traditional techniques such as Symbolic URLs or calculated fields, etc.

For your convenience, here are the links to the JavaScript files and the colorbox libary, just in case you want to attempt this on your own ;-)


have a nice day

@lex

25 comments:

Bangalore said...

Hi Alex,
Thanks for the wonderful post. Actually I tried all the steps correctly and launched the siebel in my local. But unfortunately, I am not able to see the icon for clicking the map.
I checked in the bowser code in chrome, and could only find alexmaphoverpr.js loaded via the /siebel/custom folder.

But what I believe, alexmaphoverpm.js is not getting loaded, hence it is not showing the map icon.

Do you have any idea.??
Thanks in advance Alex!!

Sablok said...

Hi @lex,

Nice Post! Thanx for sharing...

Anonymous said...

Thank you Alex, excellent!
Will give it a try soon!

Benny

Alexander Hansal said...

Hi Bangalore,

I have re-tested the scenario on a fresh installation and encountered the same difficulty.

After a bit of trial and error I found that it has to do with the manifest_extensions.map file.

A working demo which I saw had an empty [Presentation_Model] section at the very top, followed by a single line. Then the normal sections for Presentation_Model and Physical_Renderer follow.

I modified my file to contain an empty Presentation_Model section and everything worked.

Thanks for your observation, I will update the article accordingly.

have a nice day

@lex

Bangalore said...

Thanks for the reply Alex, but unfortunately its not working, I tried the approach you provided :(

- Vivek

Alexander Hansal said...

Hi Vivek,

sorry to hear that. There are a lot of pitfalls to watch out for.

For example, always use Copy+Paste when entering key values in files.

Second, ensure that all XML files are valid after modification.

Check the IMAGES folder for the map.gif icon.

Reload the browser cache.

hope this helps

have a nice day

@lex

Anonymous said...

Hi,
Quick question, if I need to permanetly display the map either in a applet on the right or below to show the selected address, do I need to create a new BC for google maps?

Regards
Vish

Alexander Hansal said...

Hi Vish,

you would do that using Symbolic URLs (no new BC required). Please check this post for more details.

have a nice day

@lex

Neel said...

Hi Alex,

Wonderful post but unfortunately not working for me.

It seems like only Physical renderer file is loaded the Presentation Model is not loaded at all.

I tried several other examples but all seem to have same problem.
Are you able to see PM loaded when you view through developer tools ???

Can we use both user properties and map file or it has to one of the two??

Thanks
Neel

Alexander Hansal said...

Hi Neel,

sorry to hear that. You can try to set the Applet User Props instead of using the file.

Also in the file, for some reason you must have an empty [Presentation_Model] section on top (as described above; most probably a bug).

have a nice day

@lex

Venkat S said...

Hi Alex,
Thanks for valuable post. I had tried with ur example above. But im facing same problem which is Presentation_Model js is not loading in browser[chrome-dev tools]. Tried all options. Using with/with out usr properties. Also ur suggestion leaving 1 more PM section w/o any value[key].
Let me if u have any solution for this. Thanks

-Venkat

Alexander Hansal said...

Hi Venkat,

this is a quite common issue now.

Neel has found out that adding the PM file to the PLATFORM_KEY_SPECIFIC section along with the PR could possibly help.

have a nice day

@lex

saurabh dixit said...

Regarding Google Maps, "customize the UI with adding google map hover" can be achieved as below,

In your custom_manifest.xml you have:
...

siebel/applet.js
siebel/pmodel.js
siebel/custom/partialrefreshpm.js


Please modify it to the following, note that in KEY Name, just the "N" is in upper case.
...

siebel/applet.js
siebel/pmodel.js
siebel/custom/partialrefreshpm.js


Clear your browser cache and re-test.


In addition to the earlier suggestion, please add the [Empty] line as the first line of the manifest_extensions.map:

[Empty]
[Presentation_Model]
Contact Form Applet = PartialRefreshPModel


[Physical_Renderer]
Contact Form Applet = PartialRefreshRenderer

Please test it further and let us know how it goes.

Thanks n Regards,

buchiela said...

Hello,
Really good tutorial, but i have a problem to follow it :(

neither physical nor presentation models are loaded (i.e their javascript files) unless i don't specify other applet changes:

[Presentation_Model]

SIS Account Entry Applet=ALEXMapHoverPModel
SIS Account List Applet=RecycleListPModel

[Physical_Renderer]
SIS Account Entry Applet=ALEXMapHoverRenderer
SIS Account List Applet=RecycleListRenderer




....

3rdParty/colorbox/jquery.colorbox-min.js
siebel/applet.js
siebel/pmodel.js
siebel/custom/alexmaphoverpm.js

-
siebel/applet.js
siebel/listapplet.js
siebel/pmodel.js
siebel/listpmodel.js
siebel/custom/recyclebinpmodel.js






3rdParty/jqGrid/current/js/i18n/grid.locale-en.js
3rdParty/jqGrid/current/js/jquery.jqGrid.min.js
3rdParty/jcarousel/lib/jquery.jcarousel.min.js
3rdParty/jqgrid-ext.js
siebel/phyrenderer.js
siebel/jqgridrenderer.js
siebel/custom/recyclebinrenderer.js



3rdParty/colorbox/jquery.colorbox-min.js
siebel/phyrenderer.js
siebel/custom/alexmaphoverpr.js

...



am i missing any configuration?

another question even when i see both files map icon is not shown :( i have checked map icon exists in images, so i guess i'm at the dead end now :(
Thanks in advance :)

Alexander Hansal said...

Hi buchiela,

sorry to hear that you're having problems. Please ensure that you follow all steps exactly. Casing is crucial, so use copy+paste instead of typing.

From a distance there is not much more I can say

have a nice day

@lex

buchiela said...

Thanks Alexander for your reply :)
if it is possible could share entire
manifest_extensions.map and custom_manifest.xml

thanks again

buchiela said...

@saurabh dixit

Thanks for your valuable comment! :) I was unlucky enough to use partial refresh as a base from
http://docs.oracle.com/cd/E16348_01/books/config_open_ui/customizing19.html
where NAME is in upper case, after change to "Name" everything worked :)

Suraj said...

Hi Alex,

Could you explain the point 6. Also I didnt find the file themetree.js in the SCRIPTS/Siebel folder.
Can that be the reason why it is not working for me?
Also I downloaded the Colorbox library and it does not have the "colorbox.css" in its root folder(I mean the folder that gets created after unzipping the downloaded file). It does have the 'colorbox.css' in its numerous examples folder.

Suraj said...

I get the following error and my applet does not load correctly

Failed to load resource: the server responded with a status of 404 (Not Found) http://autstsbldevweb2.myobtest.net/htim_enu/23016/scripts/3rdParty/colorbox/jquery.colorbox-min.js?_scb=
[View] -> Error in downloading file

Alexander Hansal said...

Hi Suraj,

the themetree.js was present in an early version I used. It seems to have been replaced by theme.js in the GA build. I have updated the post accordingly, thanks for your observation.

About the colorbox library, I found everything in place after unzipping the archive. Not sure about that. But you can place the .css file anywhere you like within the PUBLIC directory.

have a nice day

@lex

buchiela said...

Alexander,

Thanks for again for this post, it helped me understand some basic of open ui.

now i have a question because i can not figure out myself, is it possible to make really difficult customization which e.g. if you mouse over account name in the account list, show popup with 10 related quotes or something like this.

Regards,
Buchiela

Alexander Hansal said...

Hi Buchiela,

with Open UI that would be definitely possible. You would use any JavaScript framework (jQuery UI for example) to produce and populate the "floating window").

But of course stay on solid ground with what the user really needs and always design your solution for maximum performance ;-)

have a nice day

@lex

Suraj said...

Hi Alex,

Thanks for the earlier help. The colorbox.css file is not present in the root folder of the zip file downloaded. It is present in other examples folders. Could you also share the colorbox.css file that u have on the post and also the theme.js file plz?

Cheers,
Suraj

Alexander Hansal said...

Hi Suraj,

obviously the newest version of colorbox has a different folder schema. My suggestion is to use that one and refer to the .css files in the folders.

Sharing the other .js files wouldn't work well as it is my "playground" system and the files are not really cleaned up. I ask for your understanding.

have a nice day

@lex

Anonymous said...

Hi Alex,

just tried it all and it works like a charme. Looks really cool :-)

Thanks for those excellent "first steps" ... !

Have a nice weekend.

Benny