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.



18 comments:

  1. Glad you started posting on Open UI and your first post demonstrates that you have gained quite a bit of understanding of inner working on Siebel Core files.

    I was interested in definition of SiebelApp.ABCToolTips._GetToolTipsFrSvr function which you have left out :)

    So, just wanted to confirm one thing did you have to register your business services in application user properties so that they can be called from browser??

    ReplyDelete
  2. Hi Neel

    I cant let @lex have all the fun!

    _GetToolTipsFrSvr just invokes a server side BS.

    You'll need to register your business service, but consider creating a client side API, which will allow you to proxy all your calls through 1 single business service.

    Send me a message if you need more details =)

    ReplyDelete
  3. Dear Jason,
    Thanks for the wonderful post.

    I am trying to do something similar, I've started off with simple requirement, I like to show tooltip for a given applet, given control.
    I have set the
    manifest_extention file to
    [Physical_Renderer]
    BoA Contact Form Applet = BoATooltipRender
    =====
    Created BoATooltipRender.js.

    I am able to see the alert that I coded in BoATooltipRender.js

    alert("Helo1");

    Now I am trying to get hold of the control to hook tooltip. I am not able to get the control Id to my BoATooltipRender.js
    var controls=this.GetPM().Get("GetControls");
    var ctrlLastName = controls["LastName"];
    PS: I didn't have any presenation Model attached tot he applet.

    Please provide if you have any suggestions.

    Thanks again!!

    ReplyDelete
  4. Dear Mr BoA

    Your posted code snippet doesn't show how your'e getting the control id. Are you having problems getting the control map?

    Have a look at the "SiebelApp.ABCToolTips._GetControlListColSelector" which is defined in the article above, this takes the control name, and returns the jquery selector, which you can use to attach your tooltip.

    ReplyDelete
  5. I like to take one step at a time, I like to see if I can get qtip to work on one applet/one control. Tooltip JS is rendered, tooltip is not working..here is may code. Any suggestions, please help.

    if( typeof( SiebelAppFacade.BoATooltipRender ) === "undefined" ){
    SiebelJS.Namespace( "SiebelAppFacade.BoATooltipRender" );

    SiebelApp.S_App.RegisterConstructorAgainstKey( "BoATooltipRdr", "SiebelAppFacade.BoATooltipRender" );

    SiebelAppFacade.BoATooltipRender = ( function(){


    function BoATooltipRender( pm ){
    SiebelAppFacade.BoATooltipRender.superclass.constructor.call( this, pm );
    var sAppletId=this.GetPM().Get("GetFullId"),sApplet=this.GetPM().GetProxy().GetName(),sModId="JLETooltips";

    //Remove all tooltip indicators
    //$("#"+sAppletId+" img["+sModId+"]").remove();
    var controls = this.GetPM().Get( "GetControls" );
    var cntrl = controls[ "BoA Account Name" ];
    var lastNameCntrl = cntrl.GetInputName();
    $('input[name="'+lastNameCntrl+'"]').qtip({
    content: 'Stems are great for indicating the context of the tooltip.', prerender : true,
    style: {
    tip: {
    corner: 'topLeft',
    color: '#6699CC',
    size: {
    x: 20,
    y : 8
    }
    }
    }
    });

    }

    SiebelJS.Extend( BoATooltipRender, SiebelAppFacade.PhysicalRenderer );

    return BoATooltipRender;
    }());
    }

    ReplyDelete
  6. Dear Jason,
    This is Mr BoA, Thanks for the quick response, finally I am able to get it to work. For some reason vanilla qtip and qtip 2 were not working. I dont see any errors also. I tried to download qtip from craigsworks.com. That's all it started working.

    Thanks again for all the contributions your doing.

    -Venkat

    ReplyDelete
  7. Hi Venkat and everyone,

    this is a great guide line, although everystep isn't so clear. However someone could make it run.

    @Venkat: did you use the qtip from http://craigsworks.com/projects/qtip/ and just followed the Jason's instructions? We have a problem implementing tooltip in our open ui. We are using siebel open ui version 8.2.2. public sector.

    Thanks,
    Charles

    ReplyDelete
  8. Hi Charles

    The link you've referenced looks like qTip v1. You should be able to use it, but I ran across a bug that you may want to be aware of. This was mentioned in Part 1 of this article under the heading

    1. Creating the Tooltip

    @Venkat. I'm glad you got it working, you may want to confirm the above bug as well.

    The version that I used was from here http://qtip2.com/

    Regards

    ReplyDelete
  9. Hi Jason,

    Very nice article. I have a couple of questions regarding this. Can you please help out.

    1) I have one js file GlobalShowUI.js as per your artice 1 for Pre_ShowUI and Post_ShowUI, Do we need to add the tooltip loader logic to that file or should it be a seperate file all together which must be included in the manifest file.
    2) Can you also provide some more information/example of triggering the tooltip definition using a server side business service.

    ReplyDelete
  10. Hi Jason,

    We are trying to implement something similar in our project. Can you please provide an example of implementing this by calling a server side business service. For the example per say even if you can take the lov table that is fine.
    Thanks a lot in advance.

    Sukhesh

    ReplyDelete
  11. Hi Jason,

    Can you please tell me where should I put the tooltip loader logic. I tried adding it in the GlobalShowUI.js, in this file code works till tooltip loader is called in Post_ShowUI event. After that rest of the code in tooltip loader does not trigger.

    ReplyDelete
  12. Hi Sukhesh

    The tooltip loader in this article was designed to fire on a ShowUI event, however you could also use Pre/Postload. The loader definition can be loaded a separate script, or put into GlobalShowUI.js for testing purposes.

    Examples of calling server sides business services in OpenUI are shown in these two links

    1.
    http://siebel-essentials.blogspot.com.au/2013/04/siebel-open-ui-see-through-applets-part_25.html

    2.
    http://siebelunleashed.com/how-to-invoke-business-service-in-siebel-open-ui/

    It sounds like you have a syntax error in your GlobalShowUI.js. Theres not much I can suggest from afar, other than getting help from your peers to debug this further.

    ReplyDelete
  13. Thanks Jason...Had question from your script which i believe is causing the issue for me. In your script you are using the below statement in Global_PostShowUI

    SiebelApp.ABCToolTips.LoadAppletToolTips(pm);

    Just wanted to to know what is the significance of ABCToolTips here and what does it indicate.

    Even inside LoadAppletToolTips function there are couple of similar instatnce where this is used like below.

    var oAppletProp=SiebelApp.ABCToolTips._GetAppletProp(sAppletId);

    ReplyDelete
  14. Hi Sukhesh

    ABCToolTips is an arbituary namespace that I made up, it houses the tooltip methods used in this article.

    ReplyDelete
  15. Hi Jason,
    is there a chance to store the tooltip text as symbolic strings? Is there a way to retrieve them?
    Thanks, Michael

    ReplyDelete
  16. Hi Michael

    I haven't looked into it.Sounds like you need a lookup function, which you can then return to the OUI layer for display.

    ReplyDelete
  17. Hi Jason,

    I have tried implementing this poc however i have a issue. I see that my code is not going inside the if(oTooltipCfg.length>0) condition in the script.

    I have one single GlobalShowUI.js file. The code where issue occurs is given below.

    var oTooltipCfg=GetToolTipsFrSvr(sApplet);
    alert("0");
    if(oTooltipCfg.length>0){
    alert("1");
    }

    GetToolTipsFrSvr=function(sApplet){
    var oSvc = SiebelApp.S_App.GetService("DT Tooltip");
    var inPS = SiebelApp.S_App.NewPropertySet();
    var outPS = SiebelApp.S_App.NewPropertySet();
    inPS.SetProperty("AppletName", sApplet);
    outPS = oSvc.InvokeMethod("Tooltip", inPS);
    return outPS;
    }

    My BS Code is below.

    function Service_PreInvokeMethod (MethodName, Inputs, Outputs)
    {
    if(MethodName == "Tooltip")
    {
    var sBO = TheApplication().GetBusObject("List Of Values");
    var sBC = sBO.GetBusComp("List Of Values");
    var sAppletName = Inputs.GetProperty("AppletName");
    with(sBC)
    {
    ActivateField("Type");
    ActivateField("Description");
    ActivateField("Value");
    SetViewMode(AllView);
    ClearToQuery();
    SetSearchSpec("Type", "JLE_TOOLTIP_EXAMPLE");
    SetSearchSpec("Description", sAppletName);
    ExecuteQuery();
    var isRecord = FirstRecord();
    while(isRecord)
    {
    var sDescription = GetFieldValue("Description");
    var sControl = GetFieldValue("Name");
    var sDisplay = GetFieldValue("Value");
    var psChild = TheApplication().NewPropertySet();
    psChild.SetProperty("Applet Name", sDescription);
    psChild.SetProperty("Control Name", sControl);
    psChild.SetProperty("TT Display", sDisplay);
    Outputs.AddChild(psChild);
    isRecord = NextRecord();
    }
    }
    return(CancelOperation);
    }
    return (ContinueOperation);
    }

    Can you please have a look and let me know if you see any issues with the above code. Any inputs will be really helpful.

    ReplyDelete
  18. Hi Sukhesh

    length is not a valid property of PS, you need to get the child count of the PS. You'll also need to consider the output of your BS before you can put it in oTooltipCfg. The code in this article has my local dependencies, and wont work, unless the design is catered for your clients environment.

    I recommend that you engage a local Siebel Javascript expert to help you further.

    Jason

    ReplyDelete

Comments are open to all, please make it constructive.