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!



8 comments:

  1. Great post. jQuery is super handy, but I am somewhat concerned about 8.50 or greater "upgrade day" when Oracle delivers functionality that may or may not conflict with our jQuery/JavaScript customizations.

    Keep blogging!

    Mike Putnam
    http://theputnams.net/mike/peoplesoft

    ReplyDelete
  2. You're right Mike, we'll have to revisit all this code if we ever get around to upgrading. I hope to be able to dig into 8.51 soon to see what that might entail.

    ReplyDelete
  3. This is a great post. I would suggest adding a bit about just where you place this sort code as it might vary on whether one is making a local or global modification.

    ReplyDelete
  4. Bill - I always put my JavaScript into HTML objects - custom or delivered. JavaScript that applies to specific pages goes into custom HTML objects and gets inserted into the page(s) in an HTML area.

    I've added JavaScript that applies to all pages to a delivered HTML object that gets inserted into all pages by the component processor, such as PT_PAGESCRIPT.

    ReplyDelete
  5. I saw the javascript:submitAction_win0(document.win0.... in this page . I want to call this function when i click on the push button. Can you please tell me how to call the submitaction_win0 function?

    ReplyDelete
  6. Lakshmi

    As I explain in the post, all PeopleSoft internal links and buttons call submitAction_win0. (see my later post for a discussion of the function numbering http://danielkibler.blogspot.com/2013/02/why-are-some-of-peopletools-javascript.html) Altho I don't know why you would, your custom button could do the same by adding the call to the onclick attribute:

    onclick="javascript:submitAction_win0(document.win0, '#ICSearch');"

    For a link, put the call in the href attribute:

    href="javascript:submitAction_win0(document.win0,'#ICSwitchMode');"

    Dan

    ReplyDelete
  7. Thanks daniel..
    Why i am askin is...

    Current Functionality : Page has a grid and set the occurs level as 5. If they click on the "Next" Icon then next set of 5 rows will be disaplyed.

    New Functionality: Pagination need to impliment.For that I analysed the "Next" Icon functionality of the grid property. It is calling the below logic.
    --------------------
    class='PSHYPERLINK' name='MY_PAGE_FIELD_NAME$hdown$0' id=MY_PAGE_FIELD_NAME$hdown$0' tabindex='29' href="javascript:submitAction_win0(document.win0,'MY_PAGE_FIELD_NAME$hdown$0')
    --------------------

    When page loads by default 1st 5 set of rows are disaplying. Then if user clicks on the 5 button(Pagination button) then i will check the difference between previous and currnet pagination buttons and that many times i will trigger the above code.

    For me i am not getting how to trigger the above code.

    So please help me to trigger the above code when we click on the pagiantion button.

    For pagination buttons i am taking peoplesoft push buttons.

    Thanks for your support.

    Lakshminarayana



    ReplyDelete
  8. This comment has been removed by the author.

    ReplyDelete