Find Results Highlight defect

1. Log into the Application through thin client
2. Click the Binoculars
3. Choose a search category
4. Enter a search criteria
5. Click Search

The search results should show the selected record with a highlighted background as shown in the screenshot below. This feature worked in 7.8 and broke when the application was upgraded to 8.1.

Note: This issue only occurs on the thin client and cannot be replicated via the thick client.


The Find pane is rendered in SI, so the row highlighting has to be controlled by CSS and the class tag. To verify what is happening right click on the Find pane, view the source code and identify the section that has the Find results.

We can see that the class tag is missing the class name, this is why the highlighting does not work. Is the server not generating a class or is the swe command for active row highlighting broken? To get to the root cause, we need to go to web templates that is used to render the HTML before it is sent to the browser.

Goto your Web templates folder, locate and open the following file

CCListBodySearchResults.swt

The swe command that is responsible for determining the class is
<tr class="swe:this.RowStyle">

According to the Siebel documentation, this is the correct command, but the Application does not correctly render the class tags.

To work around this feature, we can utilise a swe switch statement to detect the state of the web engine, and the print out the correct class tags for our rows. Heres how its done.
<swe:switch>
<swe:case condition="Web Engine State Properties, IsCurrentRow"><tr id2='JLEOn' class="listRowOn"></swe:case>
<swe:case condition="Web Engine State Properties, IsOddRow"><tr id2='JLEOdd' class="listRowOdd"></swe:case>
<swe:case condition="Web Engine State Properties, IsEvenRow"><tr id2='JLEEven' class="listRowEven"></swe:case>
<swe:case condition="Web Engine State Properties, IsErrorRow"><tr id2='JLEError' class="listRowError"></swe:case>
<swe:default>

</swe:switch>

There are 5 available classes

ListRowOn
ListRowOff
ListRowEven
ListRowOdd
ListRowError

If you want to change the styles of these classes, they can be found in the main.css

Notice that I add a custom id2 attribute to the row tags for testing, this lets verify the change on the thick client, since the original swe command and the new switch statement will generate the exact same output, we need something to tell us that we have modified the correct SWT file.

Enjoy your new old feature again.

Cornelius' Applet Set Focus Challenge

This article continues on from the guest article on Siebel Essentials - Set Focus on an Applet by Cornelius.

When I read the article by Cornelius, and saw the ~85 lines of code, I fainted (possibly due to the heat wave on the day) and when I came to, I wondered if this solution can be made leaner?

Querying the repository is sometimes necessary, when you need to achieve non-standard functionality, but in this case I felt there had to be a better way. Using SWEApplets only works in SI, but in HI, it should be possible to read the DOM and find the first visible applet and pass it to HandleAppletClick.

I did a quick POC using the IE DTB, and got a working solution with 5 lines of code. But 5 lines was too much in my opinion, so with a re-think of the problem, I by passed HandleAppletClick, and got the solution leaner.

The result is 3 lines of code. Heres how I did it.

Before we jump into the solution, ensure you have jQuery installed on your top object. This can be done via the web templates

Insert the following line of code in a common template such as your PageContainer

<script src="<your scripts folder>jquery.min.js"></script>
<script src="<your scripts folder>common_1.00.js"></script>

Next assign the jQuery object to your top object as so in your common JS file
top.$=$;

Now you'll have access to this magic everywhere in your application.

[Solution]

Cornelius separated his solution into two parts

1. Setting focus on the applet using "HandleAppletClick"
2. Using Browser/Server script to retrieve the applet sequence from the repository

This solutions just entails 1 part

1. Goto the DOM behind the current view and click the first applet that we find =)

Line 1:

var iRows = top.$("frameset",top.SWEFindFrame(top,"_sweview").document).attr("rows");

The Siebel thin client has an array of 10 cached view frames, and randomly picks a frame to render the next view upon Application Navigate. This happens behind the scenes and is not exposed or documented. Although the frame reference is random, and the next view frame is not known, we can check to see which of the current 10 frames is visible through the "rows" attribute on the frame container.

The above line just returns a reference to the current frame index.

Line 2:

var fDoc = top.SWEFindFrame(top,"_svf"+top.$.inArray("*",iRows.split(","))).document;

With the frame index, we can grab a hold of the actual frame document, using "top.SWEFindFrame". This is an undocumented Siebel browser framework function, if you are uncomfortable using it, you can roll your own, just write a recursive function that iterates through the frames and match on the frame name or just copy the Siebel function into your own library.

Line 3:

top.$("div[onclick*='HandleAppletClick']",fDoc).first().click();

As Cornelius has discovered, Siebel uses a function called "HandleAppletClick" on the onclick handlers for its applets. My original 5 line solution involved scraping the Applet name from the DOM, and passing it to "HandleAppletClick". In this solution, I just look for the first element that has the onclick handler that fires "HandleAppletClick", and click it programmatically.

[Conclusion]

Although its not necessary to use jQuery, having it makes our solution more concise, and readable. A similiar browser script function could have been implemented, but you would be spending a lot of time performing the low level DOM work.

Applet Toggles, Personalization, and Display Visibility all affect the run time visibility of the Applet. This technique allows us to determine dynamic UI behaviour that is not available when querying the repository.

The solution is lightweight, fast and best of all, we performed a beat down of ~85 lines of browser/server script with 3 lines of "Black Magic Do" (Translation: Way of the black magic). For the non initiated, in martial arts Do, means way or road =)

EAI Phone Format Challenge feat. BRP

Over two years ago, I issued a challenge to the Siebel community, through the EAI Scriptless phone number challenge.

The original challenge was started by a reader from Siebel Essentials's scriptless challenge, which was to find a scriptless solution to strip off the formatting from a DTYPE_PHONE, and leave the raw phone number.

The challenge that I subsequently issued to the community was to find go further and find a solution which applies the format mask to the phone number without any scripting.

At the conclusion of the challenge, I presented a 35 line recursive XSLT template that demonstrated that while it is possible to go to the end of the earth to find a scriptless solution, a 10 liner business service would have between more maintainable, given that most Siebel professionals don't spend their time swimming in stylesheets.

I know that our conservative Siebel readership wont sleep well with the scripted solution, so I've been working hard on an alternate solution that will please all our declarative readers out there, and also impress the eScript Jedi's and hopefully convert them back to the light side.

Here is the challenge outlined again.

"+610212345678[LF](00) 0000 0000"

The system must take the above DTYPE_PHONE value and convert it to (02) 1234 5678 without any scripting.

Note that the DTYPE_PHONE consists of the raw number + a line feed character + a format mask with the zeros representing the position of the number, any non zero characters represent a formatting character that must also be applied to the formatted string.

[Business Rules Processor]

This requirement can be achieved by using Business Rules Processsor (BRP).

BRP was introduced to Siebel as a FINS module around v7.8. It provides a fully declarative means of processing complex business logic.

This is an except from bookshelf about the capabilities of the BRP engine.

"The Business Rule Processor features include:

* Business logic administration through the application user interface.

* Appropriate for complex business logic; supports procedure, loops, if-then-else and switch-case constructs.

* Appropriate for business logic that changes frequently. You can modify business logic without deploying a new SRF.

* Query, read, and write access to business components.

* Error handling.

* Support logging at multiple levels for easy testing and debugging.

* Can potentially replace large amounts of custom scripts."

That list is pretty impressive, but is it up to our challenge?

Here is the psuedo code for rule that we are going to implement.

1. Separate the DTYPE_PHONE into separate PHONE and FORMAT components

2. Start at the last character of the FORMAT component

3. If the character is a Zero, then take the corresponding character in the PHONE component and store it in RESULT

4. If the character is non-Zero then take the current FORMAT character and store it in RESULT

5. Move to the next preceding character and continue processing until no FORMAT characters are left

[Solution]

1. Goto the Business Rule Processor Screen

a. Create a new process called "EAI Phone Format"
b. Create the variables for our rule as shown in the screenshot below


2. Goto the Procedure view

a. Create a procedure called "DeterminePhoneString"
b. Create a procedure called "Main" and set this as the Entry procedure into this rule.
c. Create 4 steps as shown


3. 1st Step - Get CRLF character

Here we call the SSE Address Parser BS to get our CRLF character and assign it to the sCRLFChar property.


4. 2nd Step - Init variables

We initialise and setup all the variables before we start our routine.


5. 3rd Step - Loop through pattern

We define a loop that iterates through the pattern backwards, until our cursor reaches the beginning. On each iteration, it will call "DeterminePhoneString".


6. 4th Step - Set remainder of the string to the final output (Optional)



7. Define the "DeterminePhoneString" procedure

Create a switch statement with the following logic


8. Create a default branch for our switch statement



9. Activate your rule, and test it in BS simulator using "FINS CAP Processor Service"



[Conclusion]

I've only scratched the surface of BRPs features here with this example, but amongst some of the more interesting features, BRP introduces a new property type called 'Vectors" which provides quick access to a hash table like reference, which is populated directly from a BC.

Is BRP the holy grail of declarative configuration?, In my opinion no. When used badly, the problems associated with scripting will only be shifted to a declarative interface. As with scripting, BRP can be the source of duplication and spaghetti procedures.

BRP is also limited by its expressions, and does not support custom functions, so you will still need script to provide a wholistic solution. Eg. Regex is not supported, so performing email validation still requires you to call a business service.

Scripting can be used for good, but too often it falls in the hands of in-experienced developers, which is why I really like BRP. It can be picked up really quickly by beginners, as it utilises the Siebel Query Language, and provides the flexibility of eScript through declarative loops, if/else/switch conditional statements and procedures. It also forces developers to break down their routines to manageble statements that improve readability of the design.

Where BRP really shines, is the capability it provides to developers to implement the majority of requirements with programming constructs but in a declarative interface, and more importantly since the logic is outside the SRF, changes to business logic can be made outside of releases.

Solution - Popup Applet "X" button

This is the long awaited solution to the Popup Applet X button challenge, which was solved by Master configurator Michael Feng.

[Background]

Siebel provides us with two events on popup applets, represented by the buttons "Ok" and "Cancel" which provides the neccessary hooks to capture most business requirements around closing of popup applets.

But the "X" button is an exception case, where the user forced closed the applet without completing the required interaction. In these cases, you might want to rollback any changes that were made since the applet was opened, or popup an alert to educate the user of the consequences of not closing the Applet properly.

[Siebel popup applets]

It is important to understand what happens when we click on the "X" on a popup applet in Siebel. The developer who requested help from the Oracle forums, tried attaching a custom method on window.onunload, which is a native browser event for when the current view has been navigated away from or closed.

This is the right line of thinking, and isnt far off from Michael Fengs solution below. This would normally work for traditional web apps, but Siebel dosnt close the popup applet when you click "X", it just minimizes it and hides it under your taskbar, which is why the window.unload event never fires.

The way to see this in action, is to set your taskbar to auto hide, watch what happens when you click "X" on a popup applet. You'll see it minimized to a short title bar on the bottom left of your screen, underneath where the taskbar would normally sit when displayed.

[Usability]

The reason why Siebel does this is for usability. When you log into your Siebel application, the first time you open any popup applet will always be the slowest, thats because Siebel has to spawn a new instance of an IE window. When you click "X", siebel will minimise and hide the window. The next time when you open another popup applet, Siebel will re-display and re-use this same popup window.

Re-displaying an existing window is in order or magnitudes faster than opening a brand new window, and greatly enhances the user experience in HI applications.

[Solution]

As mentioned in the original post, there are various solutions to this problem. When the popup applet is closed, focus will return back to the control/applet that invoked the popup applet, and you could, pop up a confirmation at this point, but it is ugly, if the popup applet is used in many places then you would have code on a lot of base applets.

Since the Popup applet is never closed, another idea is to set a looping timer to determine the position of the applet, and if it disappears below the taskbar then we know it has been closed. Its not a desirable solution as polling adds overhead. So the ideal solution is to trap the X event on the popup applet itself.

One alternative is to attach a custom function to the window.onmove event of the Popup applet which would give us a real time notification everytime the applet gets moved, and if it moves to the taskbar position, we know the user clicked X.

Or we could do better by detecting when the physical dimensions of the Popup applet has been shrunk by monitoring the window.onresize event.

Heres how it can be done, the following code is provided by courtesy of Michael Feng.

function Applet_Load () {
var oCon = this.FindActiveXControl("NewQuery");
var obody = oCon.document.body;
var odiv = oCon.document.createElement("script");
odiv.type = "text/javascript";
odiv.language = "javascript";
odiv.text = "window.onresize=UnloadTrigger;function UnloadTrigger(){if(document.body.clientHeight==0&&document.body.clientWidth==0){alert('You close the popup applet');}}";
obody.appendChild(odiv);
}

An interesting question is, what happens to the UnloadTrigger that is attached to our first popup window, when a second popup is called after the first? Does it still linger around, even when if its unwanted on the second window? Siebel re-renders the entire contents of the applet everytime it is "re-used", but keeps a cache of the last applet.

Base applet --> Popup Applet A --> Popup Applet B

Siebel will cache the contents of Popup Applet A when it is opened, Siebel re-uses the popup window, and renders the HTML for Popup applet B when it is opened.

When Popup Applet B is closed, Siebel will reuse the popup window, and restore Popup Applet A from its cache, therefore restoring our UnloadTrigger.

So we have to modify the solution to deal with the fact that popups can spawn other popups.

function Applet_Load () {
var oCon = this.FindActiveXControl("NewQuery");
var obody = oCon.document.body;
var odiv = oCon.document.createElement("script");
odiv.type = "text/javascript";
odiv.language = "javascript";
odiv.text = "window.onresize=UnloadTrigger;function UnloadTrigger(){if(document.body.clientHeight==0&&document.body.clientWidth==0&&!Top().SWEPopupGainFocus()){alert('You close the popup applet');}}";
obody.appendChild(odiv);
}


The above code uses and undocumented function Top().SWEPopupGainFocus() to determine if a popup is in focus or not. We only want to display a message when there is no popup open.

The last piece to this puzzle is to handle the positive events when the user click on "Ok" and "Cancel", in which event we do not want to display our message. You'll need to set profile attributes, or view variables to indicate that the applet was closed properly, and check for these variables in the unload function.

function Applet_Load () {
var oCon = this.FindActiveXControl("NewQuery");
var obody = oCon.document.body;
var odiv = oCon.document.createElement("script");
odiv.type = "text/javascript";
odiv.language = "javascript";
odiv.text = "window.onresize=UnloadTrigger;function UnloadTrigger(){if(document.body.clientHeight==0&&document.body.clientWidth==0&&!Top().SWEPopupGainFocus()&&App().GetProfileAttr('ProperlyClosed')!='Y'){alert('You close the popup applet');}}";
obody.appendChild(odiv);
}

Thanks to Michael for providing this solution, and for helping solve another impossible problem and breaking new grounds on creativity =).