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



Reactions:

0 comments:

Post a Comment

Comments are open to all, please make it constructive.