Sunday, November 29, 2015

PeopleTools 8.54+ - Branding - Part 5C - Fluid Branding (Continued)

This is a continuation of my previous posts on PeopleTools Fluid Branding: Part 5A and Part 5B.

Using jQuery to override Fluid Branding Images and Icons:

Those who read my previous post (Part 5A) would realize that in order to override the delivered images and icons on the Fluid Branding we ended up customizing some of the Fluid pages and subpages. We did that because all those images and icons were referenced directly on the page field properties of the Fluid header/footer.

The question is whether we can use CSS, javascript or a combination of both to override these images and icons? The answer is yes but I would continue to ask the question if the juice is worth the squeeze? Do we really need all this additional client side code/overrides just to avoid a handful of customizations to page field properties? I will let you make that determination depending on your circumstances and environment.

In this section I will try to provide an alternative option using jQuery to avoid making customizations for image/icon overrides. You could also achieve the same using pure CSS, javascript or both but I find the jQuery library quite useful for such DOM manipulation.

As an example for the purpose of this section, I reverted the change I made previously to override the Fluid home logo image. Now the page field property is as delivered and we can see the Oracle logo on the Fluid homepage when we login.

App Designer > File > Open > Page > Select: PTNUI_LP_HEADER



In my previous post (Part 5B), I detailed how I created a online configuration framework to inject javascripts into Fluid UI. I used this framework to inject jQuery (PT_JQUERY_1_6_2_JS) and my custom javascript object (CSK_FL_IR_JS) for image replacement.


Now let us take a look at the javascript object CSK_FL_IR_JS.


In this javascript, I called an IScript that would resolve the suitable URL on the web server (cache directory) for the custom image object CSK_LOGO_SVG and store it in a javascript variable called 'cskLogo' (refer Part 5B for more details). Then I used jQuery to set/override the 'src' attribute for the image element with id 'PTNUI_LP_HEADER_PTNUI_LOGO'.

JavaScript for reference:

// Get Image URL for CSK_LOGO;
document.write(unescape("%3Cscript src='/psc/" + site + "/EMPLOYEE/EMPL/s/WEBLIB_FL_CSK.ISCRIPT1.FieldFormula.IScript_Set_Image_URL_Variable?var=cskLogo&img=CSK_LOGO_SVG' type='text/javascript'%3E%3C/script%3E"));

var $jq = jQuery.noConflict();
$jq(document).ready(function(){

  $jq('#PTNUI_LP_HEADER_PTNUI_LOGO img').attr('src', cskLogo);

});


Now we can test the changes and see how we could simply override the source attribute of an image using jQuery to avoid customizations to page field properties.


Creating an environment specific header for Non-Production databases:

This section is intended to help us avoid what I like to call "testing in production"! I am sure we have all worked in IT long enough to realize that accidents happen. I have seen many cases where we end up making changes in the production environment without realizing we are in production (causing all sorts of issues). This typically happens when we have end users and/or testers working on multiple environments across multiple browser windows. Traditionally, most organizations (with PeopleSoft applications) tend to add some visual cues (such as writing the database name on the header) to indicate/differentiate the fact that the user is currently logged in to a test environment.

In Classic UI, we had plenty of real estate in the branding header to add the database name. In Fluid UI, since we might not have enough wiggle room in the delivered header we will need to be a bit more creative. My approach to this requirement for Fluid is to add an additional custom header (that can be hidden - in cases were we want to take screenshots for job aids, etc.). While it does take up a small portion of the real estate, this was the least intrusive method that I could think of (without affecting existing functionality around the Fluid branding header).

Step 1: Create custom javascript object


This custom javascript object uses the jQuery library to add (using 'prepend' method) a custom div to the beginning of the delivered header element with id='PT_HEADER'.


Custom div structure:
div (id = cskdbnamecontainer)
- Text with database name (id = cskdbname)
- 'Hide' link to remove the header if required (id = cskdbnamehide)

Additionally, the javascript object (CSK_FL_DBNAME) also contains code to remove the custom div (cskdbnamecontainer) when we click on the 'Hide' link.

Javascript for reference:

// Display Database Name using jQuery once the document is ready;

var $jq1 = jQuery.noConflict();

$jq1(document).ready(function(){

   // Add div to display DB Name;
   $jq1( "#PT_HEADER" ).prepend( "<div id='cskdbnamecontainer' align='center'><span id='cskdbname'>Test Environment: %dbname</span><a href='#' id='cskdbnamehide'>Hide</a></div>" );

   // Add jQuery to hide the DB Name when the 'Hide' link is clicked;
   $jq1("#cskdbnamehide").click(function(){
         $jq1("#cskdbnamecontainer").hide();
   });

});


Step 2: Inject custom styles

In part 5A, we saw how we could inject custom styles into Fluid UI. Now let us add some additional custom styles to CSK_FLUID_CSS style sheet object so that it gets injected at runtime.

Note: If you are performing these steps in PT 8.55, then instead of using CSK_FLUID_CSS, you will need to make the changes to CSK_BRAND_FLUID_TEMPLATE (Click here for more details).


Custom styles for reference:

/* Fluid - Display DBName for non-Prod environments */
#cskdbnamecontainer {
   height: 26px;  
   background-color: rgb(204,204,204);
}
#cskdbname {
   color: rgba(229,47,0,1);
   line-height: 26px;
   font-weight: bold;
   padding-right: 10px;
}


Step 3: Inject custom javascript CSK_FL_DBNAME into Fluid UI

Let us inject this javascript for all non production databases (we don't want to display this in production).


Javascript injection snippet for reference:

// CSK Add Custom Environment Header;
if ("%dbname" !== "PROD_DBNAME") {

  document.write(unescape("%3Cscript src='/psc/" + site + "/EMPLOYEE/EMPL/s/WEBLIB_PTBR.ISCRIPT1.FieldFormula.IScript_GET_JS?ID=CSK_FL_DBNAME' type='text/javascript'%3E%3C/script%3E"));

}


Now we are ready to test this change.




If we click on the 'Hide' link on the custom header, then the entire div gets removed as shown below.



Adding Custom Links to Fluid Branding:

In my previous posts (Part 3 and Part 4B), we saw how we could add custom links to the Classic Branding Header. In this section, I will detail how we can add custom links to the Fluid Branding. Fluid Header as such has very limited real estate (unlike Classic), so we need to find the appropriate location to add the custom links without adversely affecting any of the delivered functionality. The best way to add custom links (with minimal impact) would be as additional list items on the "Action Menu" aka the "Hamburger" icon.



Let us say, we want to add a custom link to an external website which should be available on all Fluid Pages (including the Landing Page).

How do we add a custom link to the Fluid "Action Menu"? First, we need to figure out how the Action Menu is getting generated. If we open PT_HEADER_ACTION page in Application Designer then we can see that all delivered links are list on this page. We can also see that each delivered link is wrapped in a group box and contains specific styling. The links on the PT_HEADER_ACTION page are additionally controlled via JavaScript, CSS media queries and/or PeopleCode to display/hide links according to the device form factor. In our case, we want our custom link to always appear on all pages regardless of device form factor.


Notice there is a small area right at the top of the page which contains a "Custom Action" group box? It appears that Oracle has already anticipated the need for custom Action Menu links and provided a placeholder group box. Let us see how we can add our custom link(s) into this group box.

Step 1: Create Custom Subpage

Instead of directly adding our custom links to the PT_HEADER_ACTION page, let us create a custom subpage so that it acts as a container for all the custom links on the Fluid Branding.

The custom subpage (CSK_HEADER_ACTION) contains just two fields (one groupbox for styling and one hyperlink which is the custom link).



Page Field Properties for Group Box "CSK Home":

Add 'Default Style Name' as ps_menuitem.


Page Field Properties for Hyperlink "CSK Home":

Set the Hyperlink Destination to 'PeopleCode Command' so we can control the OnClick event using the FieldChange PeopleCode.


Always better to use a Message Catalog entry for Labels.


Step 2: Write Custom PeopleCode for the Custom Link FieldChange Event


PeopleCode for Reference:

Local string &redirectURL;

&redirectURL = GetURL(URL.CSK_FLU_HOME_ACTION_ITEM);

%Response.RedirectURL(&redirectURL);


URL Definition for Reference:


Updated on 04/26/2018

Please refer the below blog post for updated code/logic.
https://pe0ples0ft.blogspot.com/2018/04/adding-custom-links-to-action-menu.html

Step 3: Add Custom Subpage to PT_HEADER_ACTION

This section updated on August 26, 2017:

Gaurav Rajput reported an issue with Custom Action List items which made me realize that adding our custom subpage inside the 'Custom Action' groupbox was interfering with other custom (page level) action items that are included at a transaction level (click here for more details). To fix the reported issue, I moved the custom subpage outside the 'Custom Action' groupbox as follows (regardless of PeopleTools version this is a better approach moving forward):


Order of the custom subpage should also be above the 'Custom Actions' groupbox:


We are now ready to test these changes.




Note: The above steps detail how we can add custom links to Fluid universally across the application (for all pages). If you want to add custom links to a specific page (custom or delivered) only, then you can follow the approach used on PT_LANDINGPAGE (delivered page) to add the 'Personalize' link.


Considering Small Form Factor Logo for Fluid Branding:

If you used the approach detailed in Part 5A for overriding the Fluid Homepage Logo then you might have noticed that the logo for small form factor (SFF) devices revert back to another delivered image (PTNUI_ORACLE_LOGO_SFF).


This delivered image is generated by some runtime PageActivate PeopleCode.


We have two options to override this small form factor image.

Option 1: This applies for anyone currently using the customization approach detailed in Part 5A. We can simply customize PT_LANDINGPAGE.Activate (Page PeopleCode) to use our custom SFF image instead of the delivered as follows:


Option 2: Alternatively, if we use the jQuery image replacement technique detailed above then we can improvise the javascript to appropriately replace the logo depending on the form factor.

 Update CSK_FL_IR_JS as follows:


Updated javascript for reference:

// Get Image URL for CSK_LOGO;
document.write(unescape("%3Cscript src='/psc/" + site + "/EMPLOYEE/EMPL/s/WEBLIB_FL_CSK.ISCRIPT1.FieldFormula.IScript_Set_Image_URL_Variable?var=cskLogo&img=CSK_LOGO_SVG' type='text/javascript'%3E%3C/script%3E"));

// Get Image URL for CSK_LOGO (SFF);
document.write(unescape("%3Cscript src='/psc/" + site + "/EMPLOYEE/EMPL/s/WEBLIB_FL_CSK.ISCRIPT1.FieldFormula.IScript_Set_Image_URL_Variable?var=cskLogoSFF&img=CSK_LOGO_SFF_SVG' type='text/javascript'%3E%3C/script%3E"));

var $jq = jQuery.noConflict();
$jq(document).ready(function(){

  // Get current Image Source;
  var imageSrc = $jq('#PTNUI_LP_HEADER_PTNUI_LOGO img').attr('src');


  if (imageSrc != null) {
    if (imageSrc.indexOf("PTNUI_ORACLE_LOGO_SFF") < 0) {

      // Use Regular Image;
      $jq('#PTNUI_LP_HEADER_PTNUI_LOGO img').attr('src', cskLogo);

    } else {

      // Use Small Form Factor Image;
      $jq('#PTNUI_LP_HEADER_PTNUI_LOGO img').attr('src', cskLogoSFF);

    }
  }



});


Result:


Overriding the Favorite Icon for Fluid:

I am not sure if many of us noticed this but there is a delivered icon image (LOGO_FAVICON) on every browser tab beside the page title for all Fluid pages. This image/icon is called the Favorite Icon.



Someone has already asked Oracle on how to change this favorite icon image? According to Doc ID 2004996.1, currently there is no way to override this image other than updating the delivered image (LOGO_FAVICON) with a custom image using App Designer. Also, there might be plans to incorporate this as a configurable item in the future releases. You may choose to perform the steps detailed in the Oracle Support document.

Refer the following document on My Oracle Support for more details:
E:FLUID- How To Access Favicon On Fluid Landing Pages? (Doc ID 2004996.1)

I don't think I like the idea of replacing the delivered image LOGO_FAVICON (as suggested in Doc ID 2004996.1). There would be no way to identify that we made a customization since it is an image object.

Let us look at the option of using the jQuery image replacement technique detailed in the preceding section for overriding this delivered image LOGO_FAVICON.

Once again, I am updating my image replacement javascript object CSK_FL_IR_JS. I am finding all the occurrences of LOGO_FAVICON (delivered image) under head -> link (where href attribute contains LOGO_FAVICON) and replacing it with a custom image (CSK_LOGO_FAVICON) using jQuery.


Updated javascript (CSK_FL_IR_JS) for reference:

// Get Image URL for CSK_LOGO (FavIcon);
document.write(unescape("%3Cscript src='/psc/" + site + "/EMPLOYEE/EMPL/s/WEBLIB_FL_CSK.ISCRIPT1.FieldFormula.IScript_Set_Image_URL_Variable?var=cskFavIcon&img=CSK_LOGO_FAVICON' type='text/javascript'%3E%3C/script%3E"));

// Get Image URL for CSK_LOGO (Fluid);
document.write(unescape("%3Cscript src='/psc/" + site + "/EMPLOYEE/EMPL/s/WEBLIB_FL_CSK.ISCRIPT1.FieldFormula.IScript_Set_Image_URL_Variable?var=cskLogo&img=CSK_LOGO_SVG' type='text/javascript'%3E%3C/script%3E"));

// Get Image URL for CSK_LOGO (Fluid SFF);
document.write(unescape("%3Cscript src='/psc/" + site + "/EMPLOYEE/EMPL/s/WEBLIB_FL_CSK.ISCRIPT1.FieldFormula.IScript_Set_Image_URL_Variable?var=cskLogoSFF&img=CSK_LOGO_SFF_SVG' type='text/javascript'%3E%3C/script%3E"));

var $jq = jQuery.noConflict();
$jq(document).ready(function(){

  // Get current Image Source;
  var imageSrc = $jq('#PTNUI_LP_HEADER_PTNUI_LOGO img').attr('src');

  if (imageSrc != null) {
    if (imageSrc.indexOf("PTNUI_ORACLE_LOGO_SFF") < 0) {

      // Use Regular Image;
      $jq('#PTNUI_LP_HEADER_PTNUI_LOGO img').attr('src', cskLogo);

    } else {

      // Use Small Form Factor Image;
      $jq('#PTNUI_LP_HEADER_PTNUI_LOGO img').attr('src', cskLogoSFF);

    }
  }

  // Replace Browser Favorite Icon(s);
  $jq('head link[href*=LOGO_FAVICON]').attr('href', cskFavIcon);

});


We are ready to test these changes now. We can see from the screenshot below that all occurrences of LOGO_FAVICON (delivered image) have been replaced with CSK_LOGO_FAVICON (custom image).

Wednesday, November 25, 2015

Implementing no CAPTCHA reCAPTCHA in PeopleSoft

This post is based on a great discussion concerning security on the OTN forums.

The requirement was to solve the problem of bots/hacker websites hijacking users to their malicious locations (masquerading the legitimate PeopleSoft site), scraping the credentials from the users and then posting the data into the PeopleSoft signin page in a typical phishing attack scenario.

If you read through the discussion, you will notice some very valid questions, concerns and suggestions provided by Greg Kelly from Oracle and others around how to ensure that such phishing emails and attacks can be prevented.

Outside of that discussion, traditionally CAPTCHA has been used in several websites to prevent bot attacks. Now Google's reCAPTCHA project is being used more commonly since its inception in 2009. The latest version (no CAPTCHA reCAPTCHA) has significant user experience improvements. Additionally, the reCAPTCHA API version 2.0 is now a RESTful API which makes server side validation a lot easier. Another great incentive to use Google's no CAPTCHA reCAPTCHA is the fact that it is FREE!

Before going down the path of implementing reCAPTCHA, please consider accessibility and user experience implications and test accordingly. Click here for an article that talks about accessibility features of reCAPTCHA.

Here are the steps to implement Google reCAPTCHA API version 2.0 in PeopleSoft.

Note: I am currently using a HCM 9.2 PUM Image 12 - PeopleTools 8.54.08.
Reference Document: Developer's Guide

Step 1: Sign up for an API key pair

Please click here to sign up on the Goolge reCAPTCHA website. Once you sign up for an API key pair to your site, you will be provided with two keys - secret key and site key.

Step 2: Client side integration

In this case, let us assume that we want to add reCAPTCHA to the PeopleSoft signin page. So the signin page will be our client side content where we would add the reCAPTCHA code.

Here are the instructions from the Google reCAPTCHA documentation:


Let us add the above code snippet to the PeopleSoft signin page (signin.html).

Note: We can locate the signin.html file in the following directory on the web server.
<PIA_HOME>/webserv/<DOMAIN>/applications/peoplesoft/PORTAL.war/WEB-INF/psftdocs/<site_name>/

Add the following script inclusion code just before </HEAD> as shown:
<!-- CSK reCAPTCHA - Start -->
<script src='https://www.google.com/recaptcha/api.js'></script>
<!-- CSK reCAPTCHA - End -->


 Add the following div (reCAPTCHA) in an appropriate location on the content page. I chose to add it right before the submit button which could be referenced by <input name="Submit" type="submit" title="<%=137%>" class="ps-button" value="<%=137%>">.

<!-- CSK reCAPTCHA - Start -->
    <div class="g-recaptcha" data-sitekey="<ENTER_YOUR_SITE_KEY_HERE>" align="center"></div>
    <br/>
<!-- CSK reCAPTCHA - End -->


Note: If you are on PeopleTools 8.54 then you might also want to perform the same customizations to signin_fmode.html file as well. This is due to a known issue that will be fixed in PeopleTools 8.55. Refer: E-FLUID - Fluid HomePage Sign Off Link Calls fmode=1 in URL, Skips Custom signout.html Page (Doc ID 2001761.1).

Please bounce and clear the cache on your web server domains. Let us now see the reCAPTCHA plugin in action on the client side.






We are not done yet! Once verified, the reCAPTCHA integration would additionally add a string with the name "g-recaptcha-response" to the payload when the user submits (form post). This string would be a key that is generated specifically for our site. The string needs to be validated on the server side which we will explore in the next section.

Step 3: Server side integration/validation

This is the step where we verify the string provided by reCAPTCHA for validity by making a REST API call. Instructions from Google reCAPTCHA documentation.


For more details: Refer API documentation.

In this case, since we are trying to validate the reCAPTCHA response string provided by the PeopleSoft signin page, the best place to add the validation logic would be in the SignOn PeopleCode.

Let us now add a new row/entry in the sequence of Signon PeopleCode events as shown in the following screenshot. I am referencing a custom function name which I will explain shortly.


Record: CSK_RECAPTCHA
Field Name: FUNCLIB
Event Name: FieldFormula
Function Name: CSK_RECAPTCHA_CHECK

Note: After making any changes to Signon PeopleCode we must bounce the app servers for them to take effect.

Now let us see how to write some custom code to validate the reCAPTCHA response string. For reference, I pasted the entire peoplecode at the very end of this post.

Click on image to zoom in!


Note: In this case, I am using Apache Commons HttpClient Java Libraries to make the REST API call. Please refer to my previous blog posts that have more details on this topic (Part I and Part II).

Details on custom function validate_reCAPTCHA_JSON is provided below. For the purposes of JSON parsing, I am using a server side scripting technique described by Jim Marion in his blog post

Click on image to zoom in!


Message Catalog Entry that contains the JSON Parsing Script:


Script for reference:

var result = (function() {

     var json = JSON.parse(json_string);

     if (json.success) {
          return "true";
     } else {
          return "false";
     }

}());


This completes our server side integration/validation. Now we are relatively free of bot attacks! :)

Note: The same feature can be applied to any content page within the PeopleSoft application as well. If not easier, the effort should be the same as what we just went through in this post.

PeopleCode Functions for reference:

Function validate_reCAPTCHA_JSON(&json As string) Returns boolean;
  
   Local string &success;
  
   /* Instantiate ScriptEngine Manager and Engine objects */
   Local JavaObject &manager = CreateJavaObject("javax.script.ScriptEngineManager");
   Local JavaObject &engine = &manager.getEngineByName("JavaScript");
  
   /* Pass JSON string */
   &engine.put("json_string", &json);
   /* Get JSON Parse JavaScript from Message Catalog */
   Local string &jsFunction = MsgGetExplainText(26000, 1, "NONE");
  
   If &jsFunction <> "NONE" Then
      &engine.eval(&jsFunction);
      &success = &engine.get("result").toString();
   End-If;
  
   If Upper(&success) = "TRUE" Then
      Return True;
   Else
      Return False;
   End-If;
  
End-Function;

Function CSK_RECAPTCHA_CHECK()
  
   /* Get reCaptcha parameter from the Request */
   Local string &reCAPTCHA = %Request.GetParameter("g-recaptcha-response");
  
   If &reCAPTCHA = "" Then
      /* Fail Authentication */
      SetAuthenticationResult( False, %SignonUserId, "", False);
   Else
      /* Do further validation if required. Refer: https://developers.google.com/recaptcha/docs/verify */
      /* Post user's reCAPTCHA response and server's private key to API: https://www.google.com/recaptcha/api/siteverify */
      try
        
         /* Using Apache HttpClient for REST - Post Method */
         Local JavaObject &jHttp, &jMethod, &filePart, &partArray, &mPartReqEntity;
        
         /* Initialize HttpClient and set parameters */
         &jHttp = CreateJavaObject("org.apache.commons.httpclient.HttpClient");
         &jHttp.getHttpConnectionManager().getParams().setConnectionTimeout(20000);
        
         /* Initialize PostMethod */
         /* !!!!!! Avoid hardcoding !!!!!!!! Replace below URL with a URL definition that is configurable. */
         Local string &sURL = "https://www.google.com/recaptcha/api/siteverify";
         &jMethod = CreateJavaObject("org.apache.commons.httpclient.methods.PostMethod", &sURL);
         &jMethod.setFollowRedirects( False);
        
         /* Add Multi-Part Message - Start */
         /* Create String Part - secret */
         &secretPart = CreateJavaObject("org.apache.commons.httpclient.methods.multipart.StringPart", "secret", "<ENTER_YOUR_SECRET_KEY_HERE>");
         /* Create String Part - response */
         &responsePart = CreateJavaObject("org.apache.commons.httpclient.methods.multipart.StringPart", "response", &reCAPTCHA);
         /* Add Parts to Part Array */
         &partArray = CreateJavaObject("org.apache.commons.httpclient.methods.multipart.Part[]", &secretPart, &responsePart);
         /* Create Multi-Part Request Entity */
         &mPartReqEntity = CreateJavaObject("org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity", &partArray, &jMethod.getParams());
         /* Add Multi-Part Request Entity to the Post Method */
         &jMethod.setRequestEntity(&mPartReqEntity);
         /* Add Multi-Part Message - End */
        
         /* Invoke PostMethod */
         Local integer &return = &jHttp.executeMethod(&jMethod);
         Local string &responseBody = &jMethod.getResponseBodyAsString();
        
         If (validate_reCAPTCHA_JSON(&responseBody)) Then
            /* reCaptcha success: allow user */
            SetAuthenticationResult( True, %SignonUserId, "", False);
         Else
            /* reCaptcha failure: deny */
            SetAuthenticationResult( False, %SignonUserId, "", False);
         End-If;
        
      catch Exception &ex
         /* Unknown Exception */
         SetAuthenticationResult( False, %SignonUserId, "", False);
      end-try;
   End-If;
End-Function;

 

Monday, November 23, 2015

PeopleTools 8.54+ - Branding - Part 5B - Fluid Branding (Continued)

Before I start this post, I must apologize for the delay. This post has been on my "to do" list for a very long time but unfortunately other priorities had pushed it back. I finally managed to find time to go over some of the additional ("more advanced") topics that I wanted to cover on Fluid Branding continuing on my previous post Part 5A (using HCM 9.2 PUM Image 12 - PeopleTools 8.54.08).

It is common knowledge by now that there are several new configurable branding capabilities coming our way in PeopleTools 8.55 - particularly for Fluid. I learnt from various presentations at Oracle OpenWorld 2015 that moving forward - starting from PeopleTools 8.55 - there will be only one Branding (or in other words navigation experience) and it would be Fluid based. With that said, I still feel that this post would be useful/relevant for several folks who are currently on PeopleTools 8.54 or planning to upgrade to PeopleTools 8.54. Given that PeopleTools 8.55 has an expected GA perhaps by end of this year or early next year, it would still be a while before everyone gets in on the uptake!

Before I dive deeper, I want to share couple of topics (utilities) which will help us with some of the more advanced branding requirements.

Global JavaScript Injection Bootstrap for Fluid UI:

In my previous branding posts, we saw how we could inject custom style sheet overrides for Fluid UI using the online configuration options available in PeopleTools 8.54 (Assemble Themes - Global Override Style Sheet - follow the link for more details). This online configuration provides a mechanism to easily override delivered styles without any customizations.

But one of the major let downs with Fluid in 8.54 (at least from my perspective) is the fact that there is no way to inject javascript globally using a configuration (similar to style sheets). If we recall from my previous posts, we used the Branding System Options to inject Google Analytics javascript globally across the application for classic components (follow the link for more details). Once again, this online configuration ensures that the listed javascripts are injected in all components without any need for customizations. This helps us to avoid some of the hacks we used in the past for such injection e.g.: customizing the delivered javascript objects (PT_COMMON, PT_PAGESCRIPT, etc.). But unfortunately, the javascripts listed under 'Branding System Options' are not getting injected into the Fluid UI components.

Another disappointment for me (with regards to Fluid in PeopleTools 8.54) was that the jQuery and jQuery UI javascripts that were delivered for classic (injected globally as part of the Branding Header Definition - refer screenshot below) are NOT part of any of the Fluid UI pages/components. This is exasperated by the fact that we cannot really inject our custom javascript objects globally into Fluid!


The ability to inject javascript objects is very important for performing some of the more advanced branding tasks (which should be reasonably clear from the my preceding introduction and examples). With that in mind, I wanted to create my own framework (or bootstrap if you will) to inject custom javascript objects into Fluid UI pages. I am sure there are several ways to "skin the cat" but here are the principles or drivers for my approach:

- Should be least intrusive from a customization point of view. That is to customize the least amount of objects.
- Should be highly configurable. That is to build a bootstrap configuration that allows us the flexibility to dynamically add/remove javascripts objects online (on the fly) without repeatedly customizing.

Note: I ended up adding one line of custom code to a delivered PeopleCode object. Other than that, this framework is entirely configurable online (using the Branding Objects page).

Step 1: Create a custom javascript object

Navigation: Main Menu > PeopleTools > Portal > Branding > Branding Objects (JavaScript Tab)

Note: This javascript object needs to be added online so that it provides a configuration to inject additional javascript objects.

Let us create CSK_FL_BOOTSTRAP_JS as shown in the screenshot below. I will explain the placeholder javascript later as we start injecting other javascripts (using this javascript as a configuration).


Placeholder JavaScript Code - Updated on 20160403:

Updated on 20160403: I found a better way to dynamically retrieve the site name using the delivered getSiteName function (part of PT_COMMON javascript object) which seems to be a lot more efficient than my previous approach.

// This section was commented on 20160403
// Conditional logic for site name which might vary based on current DB instance;
// var dbname = String('%dbname').toUpperCase();
// if (dbname == "HCM92012")
// {
//   var site = "ps";  
// }
// Repeat preceding logic for other instances (DEV, TEST, QA & PROD);
// This section was commented on 20160403

// This line was added on 20160403
// Use delivered getSiteName function for dynamically retrieving the site name for different environments.
var site = getSiteName();

 
// -- NOT REQUIRED (20160403) -- Repeat preceding logic for other instances (DEV, TEST, QA & PROD);
// E.g.:
// if (dbname == "HCMDEV")
// {
//    var site = "psdev";  
// }

// Inject javascript(s);


Step 2: Add custom code to PT_HEADERPAGE.Activate (Page Activate PeopleCode)

PT_HEADERPAGE is a header page that is part of all Fluid UI components and is mainly used for navigation purposes. We will be adding a line of code to inject our bootstrap javascript using peoplecode.


Custom PeopleCode:

/* CSK Custom Javascript Bootstrap for Fluid - Start */
AddJavaScript(HTML.CSK_FL_BOOTSTRAP_JS);
/* CSK Custom Javascript Bootstrap for Fluid - End */


Step 3: Inject Javascript

Now that we have our custom javascript object (CSK_FL_BOOTSTRAP_JS) injected into all Fluid UI components, we can use that as a configuration to further inject other javascript objects.


Custom JavaScript (CSK_FL_BOOTSTRAP_JS):

// This section was commented on 20160403
// Conditional logic for site name which might vary based on current DB instance;
// var dbname = String('%dbname').toUpperCase();
// if (dbname == "HCM92012")
// {
//   var site = "ps";  
// }
// -- NOT REQUIRED (20160403) -- Repeat preceding logic for other instances (DEV, TEST, QA & PROD);
// This section was commented on 20160403

// This line was added on 20160403
// Use delivered getSiteName function for dynamically retrieving the site name for different environments.
var site = getSiteName();


// Inject javascript(s);


// jQuery - PT_JQUERY_1_6_2_JS;
document.write(unescape("%3Cscript src='/psc/" + site + "/EMPLOYEE/EMPL/s/WEBLIB_PTBR.ISCRIPT1.FieldFormula.IScript_GET_JS?ID=PT_JQUERY_1_6_2_JS' type='text/javascript'%3E%3C/script%3E"));

// CSK Fluid Image Replacement JS;
document.write(unescape("%3Cscript src='/psc/" + site + "/EMPLOYEE/EMPL/s/WEBLIB_PTBR.ISCRIPT1.FieldFormula.IScript_GET_JS?ID=CSK_FL_IR_JS' type='text/javascript'%3E%3C/script%3E"));
 

  • The reason I am using the delivered IScript (IScript_Get_JS) in conjunction with document.write is because I am not able to use %JavaScript meta-HTML functions in javascript objects (in the same fashion as we do in HTML objects). Perhaps a future enhancement if anyone from Oracle is listening? :)
  • The delivered IScript provides a way to resolve the javascript (at runtime) to the URL suitable for referencing the .js file on the web server (cache directory).
  • I am using the custom variable site as a mechanism to make this javascript production ready and database refresh proof. This would allow the javascript to dynamically determine the sitename using variable substitution instead of hardcoding.

In the above example, you can see that I used the javascript object, CSK_FL_BOOTSTRAP_JS, as an online configuration to inject additional javascript objects with the following line of code.

document.write(unescape("%3Cscript src='/psc/" + site + "/EMPLOYEE/EMPL/s/WEBLIB_PTBR.ISCRIPT1.FieldFormula.IScript_GET_JS?ID=PT_JQUERY_1_6_2_JS' type='text/javascript'%3E%3C/script%3E"));


IScript for Image Object Source URL Resolution:

If we look into the WEBLIB_PTBR.ISCRIPT1.FieldFormula, we will find several useful IScripts similar to IScript_GET_JS that we used in the preceding section.

Since using meta-HTML such as %Image and %JavaScript does not seem to work in JavaScript objects, I tried to see if there was an IScript which might possibly return the URL suitable for referencing an image object on the web server (loaded on the cache directory). I did not find any delivered IScripts that did something like that so I wrote my own version.

WEBLIB_FL_CSK.ISCRIPT1.FieldFormula - IScript_Set_Image_URL_Variable


PeopleCode for Reference:

Function IScript_Set_Image_URL_Variable
 
   Local string &var = %Request.GetParameter("var");
   Local string &img = %Request.GetParameter("img");
  
   Local string &html = GetHTMLText(HTML.CSK_IMAGE_URL_JS, &var, &img);
  
   %Response.Write(&html);
  
End-Function;


HTML Object - CSK_IMAGE_URL_JS


Let us see this in action. Let us create our custom javascript object.

Navigation: Main Menu > PeopleTools > Portal > Branding > Branding Objects (JavaScript Tab)



JavaScript for Reference:

// Get Image URL for CSK_LOGO;
document.write(unescape("%3Cscript src='/psc/" + site + "/EMPLOYEE/EMPL/s/WEBLIB_FL_CSK.ISCRIPT1.FieldFormula.IScript_Set_Image_URL_Variable?var=cskLogo&img=CSK_LOGO_SVG' type='text/javascript'%3E%3C/script%3E"));


// Print variable using jQuery once the document is ready;
var $jq = jQuery.noConflict();


$jq(document).ready(function(){
  alert(cskLogo);
});


What does this JavaScript do?
- Calls IScript (IScript_Set_Image_URL_Variable) and passes two variables (var - variable name to store image URL; img - image object name).
- Prints variable using jQuery once the document is ready for testing purposes.

Note: I previously injected the delivered jQuery javascript PT_JQUERY_1_6_2_JS object which is how I am able to use jQuery in the above javascript.

Let us now inject this javascript into Fluid UI using the Fluid Bootstrap javascript created in the previous section.


Now if we try to access any Fluid content then we should get the alert message that displays the variable cskLogo with the relative image URL substitution.


We can also access the IScript directly on the browser to see what it does behind the scenes.


In the next post, I intend to cover some advanced Fluid branding concepts such as image replacement techniques without customizations (using the utilities detailed in this post) and more. Stay tuned!

Note: You could enhance the utilities detailed in this post by writing your own custom meta-HTML which is a topic described in great detail by Jim Marion in his book,  PeopleSoft PeopleTools: Tips and Techniques - Chapter 9 - Page 350.