Wednesday, November 17, 2010

Using JavaScript in PeopleSoft: Proxy PeopleSoft Functions

In my last post, I showed you how to do dynamic field level validations in JavaScript with the help of jQuery. In this post we'll take it to the next level by doing validations on submit using a function proxy technique.

JavaScript is a very flexible language. One aspect that separates it from other languages, such as Java, is that everything is an object. This includes functions. At first glance treating a function as an object doesn't seem very useful, but, in fact, it leads to a number of cool capabilities. One of those capabilities is the ability to proxy a function.

Here's a non-PeopleSoft example to illustrate this. Let's say you have a vendor-created web page that already has a validatePage() function that is called by the two buttons, mySaveButton and mySubmitButton.


function validatePage(source) {
    if (!validateRequiredFields())
        return false;
    if (!validateDates())
        return false;
    if (source == 'mySubmitButton') {
        if (!validateTotals())
            return false;
        }
// ...
    return true;
}


We would like to insert another validation step without having to modify the existing code. We can do this by creating a proxy for the original. (This is all standard JavaScript - no jQuery.)


 (function() {
    // proxy validatePage
    var proxied = validatePage; // capture the original function
    validatePage = function() {
        // replace it with our custom version
        var result = true;
        var localArgs = arguments;
        var button = arguments[0];
        if (source == 'mySubmitButton') { result = validateTotals(); }
        if (result) {
                return proxied.apply(this, localArgs); // call the original function.
        } return false;
        };
})();


So what's happening here? The first line of our anonymous function gets a reference to the original validatePage function object. Then we replace the validatePage object with our new function. arguments is a special object available in the body of every function. It's an array of the arguments passed to the function. We use it here to access the argument of the original validatePage function call, in this case the button that called the function. If the calling button was mySubmitButton, my custom validation is run. If the validation passes, the next line calls the proxied function to perform the rest of the validations. There is a lot of flexibility here. We could have executed code after calling the proxied function or not even called it at all. I'll use this later option below when proxying the PeopleSoft saveWarning() function.

How do we apply this to a PeopleSoft page? If you dig around in a typical PeopleSoft page, you'll find that all buttons and links in the main frame that call PeopleCode on the server call the PeopleSoft JavaScript function submitAction_win0(document.win0,'CUST_PB_WRK_CUST_CRSPDSEL_PB'); where the second argument is the id of the button or link field. By proxying this function, we can inject our own validation code that is called whenever a button or link is clicked.



// proxy the peoplesoft submitAction_win0() function
// Called for all peoplecode buttons and links
(function() {
        // proxy submitAction_win0
        var submitProxied = submitAction_win0;
        var submitAction_win0 = function() {
        var button = arguments[1];
        var msg = "";
        switch (button) {
                case "SAVE_BUTTON": // Save
                        if (validateForSave()) {
                                return submitProxied.apply(this, arguments); // call the original function
                        }
                        break;
                case "SUBMIT_BUTTON": // Submit
                        if (validateForSubmit()) {
                                msg = '%BIND(:3)'; // Confirm Submit
                                if (confirm(msg)) {
                                        return submitProxied.apply(this, arguments);
                                }
                        }
                        break;
                default: // all other buttons and links
                        return submitProxied.apply(this, arguments);
                }
        };
})();



Again our proxy function gets a reference to the original function and then substitutes our new function. The switch checks which button called the function and runs a custom validation. If the validation passes, the original function is applied which, in this case, continues the PeopleSoft submit process. The default case simply applies the original function for all other buttons and links.

Proxy the saveWarning Function

To me the standard PeopleSoft save warning is counter-intuitive. I think the OK button should continue the action I originally selected while the Cancel button should cancel that action. Since my pages are exposed to an audience of non-PeopleSoft users, I decided to change it (It's good to be King!). I found that the save warning is raised by, of all things, the saveWarning() function. Here's how I replaced it. Note that I never actually apply the original function, so this proxy completely replaces the it.


var saveWarningURL;
var saveWarningProxied;
var saveWarningTarget;

//proxy the peoplesoft saveWarning(frameName,form,target,url) function
(function() {
saveWarningProxied = saveWarning; // Not doing anything with this
saveWarning = function() {
// console.log("arguments[1]: " + arguments[1]);
localArgs = arguments;
var frameName = arguments[0];
var form = arguments[1];
saveWarningTarget = arguments[2];
saveWarningURL = arguments[3];
var changed=null;

if (form)
 changed = checkFormChanged(form, null);

if (changed==null && top.frames && frameName.length>0 ) {
 objFrame = top.frames[frameName];
 if (objFrame)
   changed=checkFrameChanged(objFrame);
}

if ((changed==null) && top.frames)
checkAnyFrameChanged(top.frames);

if (changed) {
if (confirm('%BIND(:7)')) {
    open(saveWarningURL, saveWarningTarget);
                                }
}
};
})();


Here's what it looks like in action.


That's it for now. In the next post, I plan to show how to create your own modal dialog boxes to replace the standard alert (error/warning) and confirm (yes/no ok/cancel) messages.

Happy coding!



Tuesday, November 2, 2010

Using jQuery in PeopleSoft: Validating, Formatting and Totaling

In my last entry, I introduced using JavaScript and, specifically, the jQuery library to enhance your PeopleSoft pages. Now we'll take a look at how to apply this to browser-side data validation, formatting and totaling. This is the application that inspired me to look into using JavaScript with PeopleSoft in the first place.

Experienced PeopleSoft developers (and why would you be reading this if you're not one?) know that in order to force data validation and page update as soon as a user leaves a field, we must deactivate "deferred processing" so that the page will be submitted when the data changes. On pages with many fields, it can be very disruptive to efficient data entry to submit the page on each change. This also increases the load on the server. On the other hand, real-time data validation and page updates provide the user valuable feedback.

The solution is to do basic data validation, formatting and totaling on the browser-side with JavaScript.
One caveat, never rely solely on browser-side data validation. You cannot control what happens on the user's computer. It is not that hard to hack a web page and subvert the validation. Always do server-side validation also to insure that data you receive is safe and valid.
jQuery makes it easy to detect changes and take action on them. The basic code looks like this.

$(document).ready(function(){
  $('#MY_INPUT').change(function () {
    //do something here
  });
});

The .change() method causes the enclosed function to be called whenever the selected object value is changed and the object loses "focus." An object losses focus when the user "tabs out" or otherwise selects another object on the page. A similar method, .blur(), can also be used. The blur event occurs when the object losses focus, whether or not the value has changed.

Let's say we have a simple time entry page where the user will record her time for the week. Each day has an hours text input element, HOURS1, HOURS2 ... HOURS7. We can watch all seven inputs with one line of code and do some validation and totaling.

$(document).ready(function(){
 $('input:text[id^=HOURS]').change(function () {
  try{
   var hours = parseNumber($(this).val());
   if (hours < 0 || hours > 24){
    alert('Daily hours must be between 0 and 24.');
   }else{
    $(this).val(formatNumber(hours));
   }   
  }catch(er){
   alert ($(this).val() + ' is "' + er.toString() + '"');
  }
 });
 
 // parse text to number.
 function parseNumber(n) {
  var out;
  if (n == ''){
   out = 0;
  } else{
   var out = parseFloat(n);
  }
  if (isNaN(out)) {
   throw 'Not a number';
  }
  return out;
 }

 // format number to text with 2 decimal point. If 0 return ''
 function formatNumber(n) {
  var out;
  if (n == 0) {
   out = '';
  } else {
   out = n.toFixed(2);
  }
  return out;
 }
});


Each time one of the HOURS inputs is changed, this code is called. First we parse the value of the input ($(this).val()) to a number. If it's not a number, the user gets an alert. Same if the value is not between 0 and 24. Finally, the number is formatted with 2 decimal places and we update the input element value.

All this takes place on the browser without submitting the page to the server. Let's take one more step and provide a total. I'll add a totalHours method:

function totalHours() {
  var total = 0;
  $('input:text[id^=HOURS]').each(function () {
   total += parseNumber($(this).val());
  });
  $('#TOTAL_HOURS').val(total.toFixed(2));
 }

and call it from my original code:
...
   }else{
    $(this).val(formatNumber(hours));
    totalHours();
   }   
...

Note how easily I can loop through each of the HOURS inputs and total them up using the each() method.

Now, a couple PeopleSoft-specific tips.

By default, PeopleTools gives each input an id attribute equal to the concatenated record and field names. This can be awkward and sometimes not very descriptive. You can override this behavior by setting the "Page Field Name" in the "General" tab of the Edit Box Page Field properties. The value you set here will be used as the id and name of the element in the html. Just be sure the value you set is unique for the page.

The Edit Box "Display-Only Appearance" should be set to "Disabled Edit Control" in the "Use" tab of Edit Box Page Field properties. This causes the Edit Box to become an "addressable" text <input> element rather than <span> element with no id. This does cause some formatting issues depending on the browser. I'll describe my solution to this in a later post.

So now you can validate, format and total data on the browser side without having to submit your page to the server.

In my next post, I'll show you how to intercept the delivered PeopleSoft submit process so you can do validations and verifications on submit.

Wednesday, October 20, 2010

Using jQuery in PeopleSoft: Introduction

At my company, management decisions have kept most of our PeopleSoft applications on PeopleTools versions prior to 8.50. So we haven't been able to take advantage of the new AJAX features that come with the most recent versions of Tools. However, we are now working on a application that will be exposed to an large audience outside of our corporate firewall. We realized that we would like our users to have a modern browser experience including: dynamic page updates, client-side validation, modal dialog boxes. How can we do this with PeopleTools 8.49? The answer is to do our own JavaScript programming.

Having done some JavaScript for my wife's business web site GetRolling.com, I realized that using "straight" JavaScript to program modern features is a lot of work, so we went looking for a framework. We quickly settled on jQuery, a popular, open-source JavaScript library with lots of support and many add-ons. We had also heard that Oracle was using jQuery with the most recent versions of  PeopleTools (true?), so it would help "future-proof" our experience.

The core strength of jQuery is its selectors, which allow you to easily select and act on one or more DOM objects without having to traverse the DOM hierarchy. The multitude of selector options can be a bit daunting at first, but after learning some common patterns, I found them to be very efficient. While the jQuery project provides plenty of documentation on their site, I found this Ref Card from DZone very handy when trying to figure out how to write my selectors.

A key concept is that your jQuery code should be wrapped in document ready block:

$(document).ready(function(){
    // your code
});


This ensures that you code does not get called until the DOM is loaded in the browser and ready. You will use $(selector) throughout your jQuery code. $() is just shorthand for the core function jQuery(). jQuery also makes liberal use of anonymous functions like the function(){...} above.


Getting Started


I'll skip the traditional HelloWorld as it's not very instructive for our use and get right to a concrete example. Let's say you want to validate a user entry immediately after the user leaves the input field. In PeopleTools, you would need to add FieldChange PeopleCode to the field and uncheck "Allow Deferred Processing."  This would force the page to be submitted to the server whenever the field value is changed so that the validation PeopleCode can run. This can be very disruptive to the user experience and place extra load on the server.

With jQuery, we could write this (USA-centric) code:
$(document).ready(function(){
    $('#ADDRESS_ZIP_CODE').change((function () {
    if($(this).val().length < 5) {
 
alert("Please enter a valid Zip Code"); } }); });
What's happening here? # is the id selector. The fragment $(#'ADDRESS_ZIP_CODE') "selects" the object with an id='ADDRESS_ZIP_CODE'. After the DOM is ready, jQuery adds an anonymous function to the onChange event of this object that happens to be a text input element. When the value of the input is changed, the function is called. Inside the function, $(this) refers to the selected object. val() returns the value attribute string and .length is the standard JavaScript length property of the string. As you can see, you can write some very concise code with jQuery.

Here is another handy example I use in PeopleTools grids.

$('input:text[id^=HOURS]').each(function(index) {
  // your code
}


This selects each of the input text elements whose id attribute starts with HOURS. each() iterates through each element in a loop. In a PeopleTools grid, each field id is suffixed with $i where i is the row number, e.g., HOURS$2, so you can see how useful this can be.

In future blogs I'll describe some more powerful things you can do with jQuery, but I'd like to use the rest of this blog to show how to get this code on to your page.

The basic mechanism I use is to write the JavaScript in an application designer HTML object and use page activate PeopleCode to put that HTML into an HTML Area that I've included on the page. The position of the HTMLArea does not seem to matter much, but I try to place it near the top of the page.

It's important to include a reference to the jQuery library in your code. Here's an example of how it looks:


<script src="/rhijs/jquery-1.4.2.min.js" type="text/javascript"></script>

<script type="text/javascript" >
$(document).ready(function(){
 $('#ADDRESS_ZIP_CODE').change((function () {
  if($(this).val().length < 5) {
   alert("Please enter a valid Zip Code");
  }
 });
});
</script>

I had my admin put the file jquery-1.4.2.min.js on our web server in a custom directory so it can be referenced by my pages. This is the "minified" version of the library. The code is nearly impossible to read, but the file is much smaller to load and should be used for production. There also a standard version with the full source that can be used for reference and debugging: jquery-1.4.2.js.

If you don't have access to your web server, a good option, if your network allows, is to hot link to a CDN as described here: http://docs.jquery.com/Downloading_jQuery#CDN_Hosted_jQuery

The basic PeopleCode to include your JavaScript in your PeopleSoft page is:

MY_REC_DERIVED.HTMLAREA1.Value = GetHTMLText(HTML.MY_JQ_ZIP_VALIDATE);

A powerful technique is to use the text replacement functionality of GetHTMLText to "customize" your code on the fly. For instance, instead of hard coding the error message in the JavaScript, we could modify the alert line to read:

 alert("%BIND(:1)"); // note the quotes around the bind

and change our PeopleCode to:

Local string &invalidZipMsg =  MsgGet(29000, 85, "Message not found");
MY_REC_DERIVED.HTMLAREA1.Value = GetHTMLText(HTML.MY_JQ_ZIP_VALIDATE,&invalidZipMsg);

That's it for now. In future blogs I'll introduce a technique that allows you to intercept calls to delivered JavaScript, so you can trigger your own validation and verification code; show you how to format input; how to create your own modal dialogs; and how to add an Auto-Complete textbox to your page. I'll also discuss some tools that are essential to your success in browser JavaScript programming.

I'm certainly not the first one to use JavaScript with PeopleSoft. There are plenty of examples on the Internet. I'm in special debt to Jim Marions' blog and his new book PeopleSoft PeopleTools Tips & Techniques.

Sunday, October 10, 2010

I'm Back

I'm reviving my blog with a new emphasis on my professional activities as a PeopleSoft Application Architect and developer. I'm leaving my old posts up as they're fun to revisit, but in the future expect more technical content. My next post will be about using the jQuery JavaScript library to enhance the user experience for PeopleSoft Applications.