Friday, February 22, 2013

Why are some of the PeopleTools JavaScript Functions Numbered?

If you spend any time digging around in the JavaScript on PeopleSoft, you will see a lot of functions and other objects with names that end with "_win0." I always wondered why that was, but since it never seemed to change, I just went with it.

Now that we are planning a PeopleTools upgrade to 8.53 (currently 8.49), I started looking at some 8.5x applications to see what upgrade issues I might have with my custom JavaScript. I'm mainly worried about the places where I override delivered JavaScript. I inadvertently discovered that it isn't always "_win0." If you use the "New Window" link that appears on most content pages, you will note that the URI on the new page changes from .../psp/... to .../psp_1/.... If you then look at the code you'll see that "_win0" is now  "_win1."

I have yet to figure out why the JavaScript is rewritten this way. I assumed that the JavaScript in one window or tab cannot interfere with the JavaScript in another. Am I wrong? Can anyone help me out here?

By-the-way, I got lucky in my original exploits. A lot of my functionality depended on overriding or proxying delivered function submitAction_win0. This could have broken, but our custom pages did not expose the "New Window" link. I've done some thinking about how to get around this. I took a look at HTML object PT_PAGESCRIPT where much of the basic delivered JavaScript in stored. You can see that code contains replacement variable %FormName. This must get replaced somewhere in the page processing with "winx." I'll do some experimenting with that. Or I may just have to parse the %ContentURI in PeopleCode to determine the window value. I don't think it's exposed anywhere else.

-Keep Digging

Thursday, November 8, 2012

Using JavaScript in PeopleSoft: Popup Form with jQuery UI Dialog

I recently had a requirement to enable users to send an email from a specific PeopleSoft page. To be consistent with other elements of our interface, I wanted to popup a dialog with a form where the user could enter the message details. The input would then be processed on the server in PeopleCode to send the message.

Here is the dialog.



As in other areas of our public interface, most of the heavy lifting will be done by jQuery and jQuery UI. See my earlier posts for basics. By-the-way, we are still on PeopleTools 8.49. There may be a better way to do this in the world of 8.50  and beyond.

I used my standard technique of putting the JavaScript and related markup in an HTML definition. On Page.activate, the markup is injected into an HTML area on the page. I also added to the page a field for each of the user input fields that are to be displayed in the dialog. These fields are all set to Invisible - Modifiable by JavaScript. This setting allows me to create hidden fields on the page that will will be picked up by the component processor. I also gave the fields friendly names in the Page Field Name field under the General tab.

I first added the jQuery libraries, css and hidden fields to my HTML markup. The hidden field names and ids match those I entered in Page Field Name.


<script src="/rhijs/jquery.min.js" type="text/javascript"></script>
<script src="/rhijs/jquery-ui.min.js" type="text/javascript"></script>
<script src="/rhijs/date.js"  type="text/javascript"></script>
<link type="text/css" href="/rhijs/jquery-ui-1.8.5.custom.css" rel="stylesheet" />
<link type="text/css" href="/rhijs/demos.css" rel="stylesheet" />

<!-- following fields added for Client Timesheet Email -->
<input type='hidden' name='EMAIL_TO' id='EMAIL_TO'/>
<input type='hidden' name='EMAIL_CC' id='EMAIL_CC'/>
<input type='hidden' name='EMAIL_BCC' id='EMAIL_BCC'/>
<input type='hidden' name='EMAIL_SUBJECT' id='EMAIL_SUBJECT'/>
<input type='hidden' name='MESSAGE' id='MESSAGE'/>


Then the HTML for the dialog form. I'm using a combination of custom and PeopleSoft class names to style the form. The main <div> has the attribute style="display:none" to prevent the html from being shown on the page .


<!-- Send Client Email Dialog html -->
<div id="sendEmailDialog" title="Email Timesheet" style="display:none">
<style type="text/css">
.emailLabel {
display: block;
width: 90px;
float: left;
margin: 5px;
}
.emailInput {margin: 5px; }
.emailInputDisabled {margin: 5px; border: none; background-color:white; }
.dialogFormset {border: none;}
</style>
<p class="RH_PAGE_HDR">Send Timesheet by Email</p>
<p class="PSEDITBOX_DISPONLY">Type email addresses into the To, CC and BCC fields using a semicolon (;) as the separator.</p>

<fieldset class="dialogFormset">
<label for="emailFrom" class="PSEDITBOXLABEL emailLabel">From:</label>
<input type="text" id="emailFrom" class="emailInputDisabled" size="50" maxlength="254" value="%BIND(:17)" class="PSEDITBOX_DISPONLY" disabled="disabled"/><br/>
<label for="emailTo" class="PSEDITBOXLABEL emailLabel">To:</label>
<input type="text" id="emailTo" name="emailTo" class="emailInput" size="50" maxlength="254"/><br/>
<label for="emailCC" class="PSEDITBOXLABEL emailLabel">CC:</label>
<input id="emailCC" class="emailInput" size="50" maxlength="254"/><br/>
<label for="emailBCC" class="PSEDITBOXLABEL emailLabel">BCC:</label>
<input id="emailBCC" class="emailInput" size="50" maxlength="254"/><br/>
<label for="emailSubject" class="PSEDITBOXLABEL emailLabel">Subject:</label>
<input id="emailSubject" class="emailInput" size="50" maxlength="80"/><br/>
<label for="attachment" class="PSEDITBOXLABEL emailLabel">Attachment:</label>
<input id="attachment" class="emailInputDisabled" id="emailFrom" size="50"  value="%BIND(:18)" class="PSEDITBOX_DISPONLY" disabled="disabled"/><br/>
<label for="emailMessage" class="PSEDITBOXLABEL emailLabel">Message:</label>
<textarea id="emailMessage" class="emailInput" rows="6" cols="60"></textarea>
</fieldset>
</div>


When the user clicks on the email link, JavaScript pops up a jQuery UI dialog. Here's the code that does that:


// Send Client Timesheet Email 
function sendClientEmail() {
isDialogOpen = true;
sendClientEmailDialog.dialog('open');
}



// Send Client Email Dialog definition

var sendClientEmailDialog = $("#sendEmailDialog").dialog({
autoOpen: false,
width: 600,
modal: true,
buttons: {  "Send Email": sendEmail,
            Cancel: function() {
$( this ).dialog( "close" );
isDialogOpen = false;
}
                 }
    });


// Send button function for sendClientEmailDialog dialog
var sendEmail = function() {
var mail = $('#emailTo').val();
if (mail == "") {
alert("To: address is required");
return false;
}
$('#EMAIL_TO').val(mail); //hidden field
$('#EMAIL_CC').val($('#emailCC').val()); //hidden field
$('#EMAIL_BCC').val($('#emailBCC').val()); //hidden field
$('#EMAIL_SUBJECT').val($('#emailSubject').val()); //hidden field
$('#MESSAGE').val($('#emailMessage').val().replace(/\n/g,"<br/>")); //Replace cr with break
$('#RH_TC_DERIVED_CONFIRMED').val('Y'); //hidden field
$(this).dialog('close');
isDialogOpen = false;
return submitProxied.apply(window, localArgs); // continue submit process
};

In the sendClientEmailDialog definition, $("#sendEmailDialog").dialog uses the html we defined above as the body of the dialog.  The sendEmail function is called by the Send Email button. The function copies the form values into the hidden fields and continues with the normal PeopleSoft button processing. On the server, the component processor adds the hidden fields to the buffer where PeopleCode validates the input and sends the email. 


Monday, May 7, 2012

Extracting Record and Field Metadata

As part of a data integration effort, I created a program - an Application Engine - to extract useful metadata for PeopleSoft record and field definitions to an Excel spreadsheet. For good measure, it also adds some summary information about the actual data in each of the database fields.

To start I needed to dig into the PeopleTools tables where the metadata is stored. A couple of excellent references for this task are The PeopleSoft Customizer and David Kurtz's excellent book PeopleSoft for the Oracle DBA.

Here are the metadata tables I used.

  • PSRECDEFN - The record header table
  • PSRECFIELDALL - All fields in the record, including subrecord fields
  • PSRECFIELD - Record-field data. Sub-record fields are listed under the sub-record recname, so it can take several queries to get all the data for a record.
  • PSDBFLDLABL - Label data for each field with DEFAULT_LABEL = 1 being the default label
You can also get some (but not all) metadata info in PeopleCode by using CreateRecord() to instantiate a Record object and looping thru the fields.



Local array of string &records;


&records = CreateArray("JOB", "EMPLOYMENT",...);
Local Record &recfield = CreateRecord(Record.RHRECFIELD);


Local integer &i = 0;
While &records.Next(&i)
   Local string &recname = &records [&i];
   &recfield.RECNAME.Value = &recname;
   Local Record &rec = CreateRecord(@("Record." | &recname));
   Local integer &f;
   For &f = 1 To &rec.FieldCount;
      Local Field &field = &rec.GetField(&f);
      &recfield.FIELDNAME.Value = &field.Name;
      &recfield.FIELDNUM.Value = &f;
      &recfield.KEY_FLG.Value = &field.IsKey;
      &recfield.REQUIRED_FLAG.Value = &field.IsRequired;
      &recfield.YES_OR_NO.Value = &field.IsYesNo;
      &recfield.Insert();
   End-For;
   
End-While;

I added some steps to get sub-record fields, translate the field types, get the XLAT values, etc. And finally run some stats on the actual data values.

It is not a polished program. There is a little hard coding and I just run it from App Designer. If you are interested I've exported the project to a zip file. It is available to download. 

Friday, August 26, 2011

GenerateComponentContentURL() Fail

We have a custom PeopleSoft module that handles requests from another web application. After authentication, this web application initially forwards the request to an iscript that, after identifying the user, redirects her to the appropriate home page. The redirect URL is created using the built-in PeopleCode function, GenerateComponentContentURL(). Here is an example:
%This.approverHomePage = GenerateComponentContentURL(%Portal, %Node, MenuName.MY_MENU, "GBL", Component.MY_HOME, "", "");
 ...
%Response.RedirectURL(&userIdentity.getHomePageURL());

This was working fine until the web application was updated. At that point, the redirect started to fail silently. After a lot of false starts, I determined that GenerateComponentContentURL was returning an empty string, but I could not figure out why.

Long story short, the newly-configured web application was using a URL for the iscript that had an invalid node. The url was:

https://myapp.com/psc/dbname/EMPLOYEE/PSFT/s/WEBLIB_CUSTOM.NAV_FUNCTIONS.FieldFormula.IScript_Navigation

but should have been:

https://myapp.com/psc/dbname/EMPLOYEE/PSFT_HR/s/WEBLIB_CUSTOM.NAV_FUNCTIONS.FieldFormula.IScript_Navigation

This caused %Node to return PSFT, an invalid node. One would think that GenerateComponentContentURL would throw an exception in this case, but it just returned an empty string. I also find it interesting that even with an invalid node in the URL, the iscript still ran.

Thus is life customizing PeopleSoft.

Thursday, August 11, 2011

Custom Buttons on Modal Pages

There are lots of good reasons to use modal pages and components in PeopleSoft. However, one thing you sacrifice is control, especially if you use the delivered Save and Cancel buttons that PeopleTools adds to your pages. There is nothing to stop you from adding your own buttons to the pages, but if you do the delivered buttons are still there, perhaps confusing the user. At least for the modal component, there is a solution. I tried disabling the toolbar in the component internet properties, but the buttons were still there.




Then a co-worker (thanks Giri) pointed out that if you actually uncheck the Save and Cancel checkboxes, the buttons are not shown. Wow. He's right. Problem solved. Is this a bug? Probably. Btw, we use PeopleTools 8.49.


Monday, August 8, 2011

Get Rid of Unwanted Save Warning

Have you ever had a component that gave you unwanted Save Warnings. I recently had to deal with this problem on a page that had a custom button that among many other things called doSaveNow() and then a transfer. Despite calling doSaveNow, the transfer resulted in a Save Warning. I traced the PeopleCode, but could not find any change between the save and transfer that should result in the warning. Frustrated, I searched online for a solution. I found an undocumented PeopleCode function designed for this situation - SetSaveWarningFilter( True).

Calling SetSaveWarningFilter with True input parameter causes the subsequent page to ignore the SaveWarning. It's not perfect because user is shown the page that would display the Save Warning, but then routed to the next page as if the user clicked the cancel button on the Save Warning dialog. In our situation this was good enough.

Tuesday, April 12, 2011

How Select Options are Set on a PeopleSoft Page


I just learned something new about how PeopleSoft pages work. We are using PeopleTools 8.49.29. Your mileage may vary.

It appears that if more than one Select (dropdown) have the same options, PeopleTools puts the options into a JavaScript array, then populates the options with JavaScript after the page loads. I learned this the hard way because I had a jQuery function that ran on page load and uses the select values. It was not working right because the select values were not set when the script ran.

The function that populates the options and sets the selected values is setSelectElemOptions_win0(). I solved my problem by creating a proxy for this function and adding a call to my function. This ensures that my function runs after the Select options are set. See my previous post on how to create a proxy.


Here's the code.


// proxy the setSelectElemOptionsProxied function to ensure Select Options
// are set before totalHours is called on page load
var setSelectElemOptionsProxied;
(function() {
// proxy setSelectElemOptionsProxied
setSelectElemOptionsProxied = setSelectElemOptions_win0;
setSelectElemOptions_win0 = function() {
setSelectElemOptionsProxied(); // call the original function
totalHours(); // my function
};
})();