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!