Thursday, July 27, 2017

Event Mapping: FieldChange

In my post Event Mapping: Extending "Personal Details" in HCM I noted that PeopleTools 8.56 FieldChange event mapping didn't work with fields from subpages. I opened a bug with MyOracle Support and Oracle sent me a fix to test. The fix works great! Now that I have FieldChange Event Mapping working for that prior scenario, I wanted to share how that Personal Details scenario would change. First, I would remove the JavaScriptEvents line. Specifically, these two lines:

REM ** generate the target URL for the new link;
Local string &targetUrl = GenerateComponentPortalURL(%Portal, %Node, MenuName.GH_CUSTOM_FL, %Market, Component.GH_TRAVEL_PREF_FL, Page.GH_TRAVEL_PREF_FL, "");
&recWrk.HCSC_BTN_SELECT.JavaScriptEvents = "href='" | &targetUrl | "'";

Next, I create an Application Class to hold my new FieldChange event mapping PeopleCode:

import PT_RCF:ServiceInterface;

class PersonalDetailsRowClick implements PT_RCF:ServiceInterface
   method execute();

method execute
   /+ Extends/implements PT_RCF:ServiceInterface.execute +/
   When = 11 /* Travel Preferences  */
      Transfer( False, MenuName.GH_CUSTOM_FL, BarName.USE, ItemName.GH_TRAVEL_PREF_FL, Page.GH_TRAVEL_PREF_FL, %Action_UpdateDisplay);      

OK... the code is short and easy to read. Basically, it transfers to another component if a specific condition is met. I believe the confusing part is that condition. The problem that I see is that this code is missing context. Why does that condition exist? What does it mean? Where did that number 11 come from? What is HCSC_TAB_DVW.ROW_NUM? This is a problem I have with Event Mapping. My code is just a fragment with no context. Referring back to the code from Event Mapping: Extending "Personal Details" in HCM, we see that HCSC_TAB_DVW.ROW_NUM is the grid row number. So basically, if the current row clicked is row number 11, then transfer to the specified component.

When writing Event Mapping PeopleCode, it is very important that you understand the base code, the code you are enhancing. The delivered FieldChange event uses an Evaluate statement to transfer based on an identifier that happens to be stored in HCSC_TAB_DVW.ROW_NUM. Oracle is using a row number to choose the target component on click (FieldChange). I followed the same pattern and that pattern led me to number 11. But what if Oracle adds another row? Suddenly my code (or Oracle's code—order matters) breaks. Perhaps I should have used an artificial number such as 100 assuming Oracle would never add 100 items to that "collection"? I can think of few other ways I may have written the initial code that would be easier to follow. For example, what if the code that establishes the rowset also pushed the transfer parameters (menu, bar, item, etc) into fields in the derived work record? Anyways... back to Event Mapping...

The remainder of the configuration is the same as Event Mapping: Extending "Personal Details" in HCM. I create a service definition and map that service definition to the FieldChange event of HCSC_FL_WRK.HCSC_BTN_SELECT. Next step is to choose the Processing Sequence: Pre or Post. I don't have a good answer here. Either way your code will run. If you choose Pre and Oracle adds an eleventh item, then Oracle's code will win. If you chose Post, then your code will win.


Charandeep Singh said...

This is great Jim, I wasn't aware we can could do this. If we like to add new component in-between other component (not at end), I don't think this could work then? But I do see many other places where we can use this. Thanks!

Jim Marion said...

@Charandeep, in between, no, but first, yes. Your code would run first and Oracle's would never run. So if there was some delivered code you still wanted to run, you would have to copy it into your event handler and run yours first. The transfer would ensure that Oracle's code didn't run.

Charandeep Singh said...

Jim, good to hear there is workaround, will check it out. Thanks!

Charandeep Singh said...

Jim, One off-topic question, I have subscribed to your blog's RSS via Feedly, but when we click on any post, it doesn't show post body in its preview, instead it takes to post's link. Have you did something or is there an issue with RSS XML?

Jim Marion said...

@Charandeep, I haven't changed anything on my end. This URL appears to show the full post in the Atom feed:

Neil Yetman said...

Hi Jim, Re your worry about supporting components with these Event Mapping configurations, how about this for an idea? You can add a global branding object (JavaScript) that calls back to an iScript to query the Event Mapping configuration. And if there are Event Mappings for the component, then list them to the browser's console. That might give people supporting PS systems some place to start to check/rule out Event Mapping.


Jim Marion said...

@Neil, good point. I like the idea of creating tools to help identify Event Mapping.

Tom Williams Jr. said...

Hi Jim.

Great post and you got me thinking about taking advantage of the Event Framework. One challenge that recently came across my desk was to inject a prompt of some sort into a Configuration Page save event so that a user making those changes could provide comments that described why they needed to make the change. We would then audit these updates and make sure they made it into our weekly change control communications.

I've tried this using the "Prompt" command and failed due to it being a think time event. I've tried DoModalComponent and DoModalXComponent but they require a shared work record and I want my package to be able to freely bolt onto any configuration page without customization.

If you have any suggestions that might get the brain power flowing in the right direction I'd appreciate it.

I'd really like to use the Event Mapping Framework to accomplish this task.

Thanks in advance...

Tom Williams Jr.

Jim Marion said...


Great question. Basically, you have to avoid any function that causes a request/response cycle during certain events. There is no way to capture feedback from the user after the save button hits the web server and before the save event (a smart ServletFilter could do it). I believe this includes modal function calls.

Anther challenge is that Event Mapping can't add or remove fields from the component buffer (although it can SQLExec).

If I were going to make this happen with Event Mapping, I think I wouold dig into the Save event JavaScript and MonkeyPatch save. Remember, it isn't just a button click, but also a key combination (Alt-1?). This allows you to inject JavaScript into the save processing cycle. That JavaScript would do one of 2 things: 1 it might add a hidden input field to the posted form to retrieve in my Event Processing PeopleCode using %Request or 2 it might send the contents to an iScript for saving.

Neither of these is foolproof. The only true logic is the server's logic. Anytime you push logic onto the client, a smart (or devious) user can bypass that logic and send requests to the web/app server. As you have seen, however, you can't ask the user for input from Save event PeopleCode.

Tom Williams Jr. said...

Thank you Jim for your feedback.

I get a little concerned with the MonkeyPatch save from a supportability and security point of view but it would be fun just to see if I could do it in a PUM installation :).

I think I can still use the Event Mapping to log changes. I may even be able to loop through the buffer generically and log each field that had a change to a long object. I think this may get us 75% of the way there. It is unfortunate though that we cannot log a users intent. It will be interesting to see where they take the Event Mapping in the next tools releases.

Tom Williams Jr. said...

Since I brought it up Jim. I thought I'd share my code used in my app package that drives my ultimate audit solution. Well, at least it is the cornerstone. I still need to pull in the required keys but boy, does this help when one wants to enable component auditing without modifying the underlying record properties.

Credit to Garretts blog for the generic component looping solution.

import PT_RCF:ServiceInterface;

class Z_COMPONENT_AUDITOR implements PT_RCF:ServiceInterface
method execute();
method TestFieldChange(&lvl As integer, &rs As Rowset);

method execute
/+ Extends/implements PT_RCF:ServiceInterface.execute +/
Local number &i, &j, &k;
/* Level 0. */
Local Rowset &rowset0 = GetLevel0();
%This.TestFieldChange(0, &rowset0);
/* Level 1. */
For &i = 1 To &rowset0(1).ChildCount
Local Rowset &rowset1 = &rowset0(1).GetRowset(&i);
%This.TestFieldChange(1, &rowset1);
/* Level 2. */
For &j = 1 To &rowset1(1).ChildCount
Local Rowset &rowset2 = &rowset1(1).GetRowset(&j);
%This.TestFieldChange(2, &rowset2);
/* Level 3 (maximum). */
For &k = 1 To &rowset2(1).ChildCount
Local Rowset &rowset3 = &rowset2(1).GetRowset(&k);
%This.TestFieldChange(3, &rowset3);

method TestFieldChange
/+ &lvl as Integer, +/
/+ &rs as Rowset +/

Local string &strComponent, &strOperID, &strComment, &strSQL;
rem Local datetime &dtmDateTime;
Local number &i, &r, &f;
Local Field &fld;

For &i = 1 To &rs.ActiveRowCount
/* Loop through all Records in the RowSet. */
For &r = 1 To &rs(&i).RecordCount
/* Find Records that have changed. */
Local Record &rec = &rs(&i).GetRecord(&r);
If &rec.IsChanged Then
/* Loop through all the Fields in the Record. */
For &f = 1 To &rec.FieldCount
/* Find Fields that have changed. */
If &rec.GetField(&f).IsChanged Then
If &rec.GetField(&f).IsKey Then
&fld = &rec.GetField(&f);
&strComponent = %Component;
&strOperID = %UserId;

&strComment = "FIELD CHANGED - Level: " | &lvl | ", Row: 1, Record: " | &rec.Name | ", Field: " | &fld.Name | ", OriginalValue= " | &fld.OriginalValue | ", Value= " | &fld.Value;
SQLExec("Insert into ps_z_cmpnt_auditor fields(pnlgrpname, lastupdoprid, datetime_updated, comments) values (:1, :2, sysdate, :3)", &strComponent, &strOperID, &strComment);



Jim Marion said...

@Tom, This is great! Thanks!

Pat O'Hare said...

We are trying to attach some code to the Add/Update a Person component on a record sitting on a sub page. I have checked Campus PUM 07 (9.2 Tools 8.56.03) and we are unable to see the SCC_CITIZEN_H view as a record to be selected and assign the event mapping code.
I searched for your bug but could not find it.
Could you please advise what the fix was or which Tools release this will be fixed in?
We are about to apply 8.56.04 to one of our environments so this might address the problem.

Jim Marion said...

@Pat, your behavior sounds exactly like the bug. I wish I could give you details on the patch, but I don't have any. I received a fix directly through my contacts in development. Oracle eventually closed the case I opened to track the bug. I assumed they rolled the resolution right into the codeline, but the fact that you are on .03 makes me wonder. I was on .01, so the fix would definitely be there if properly documented.

Unfortunately, all I can suggest is that you open a case. If you post the incident number, I will feed it back to the developer and see if he has a response.

Pat O'Hare said...

Thanks for your response. We will wait for our update to 8.56.04 to see if it is fixed in that release.
In the mean time I have found that inserting into the PSPTCSSRVCONF record allows you to prescribe a record that you can not access via the front end. This works but gives an issue in the app server logs like
Invalid version number for SCNFM(_PTCS_PTEVMAP/HC_SCC_ADD_PERSON_GBL/GL0_CITIZENSHIP/187128192/ENG) = 0. Reset to 1

So my solution (for now) is to add a row via the front end and then do an update via
AND PTCS_INSTANCEID = '228039173' ;

My suspicion is the fix is an adjustment to the prompt record to pick records off sub pages.

Thanks Jim for your help.

PeopleTools said...

PeopleTools said...

Event Mapping support to Component Interface explained -

Jim Marion said...

Thank you for sharing the information on Component Interfaces. Several of my customers have been asking about this since Oracle says it is supposed to work, but like you noted, it wasn't working as expected.

Joe Mumbauer said...

I'm using Event Mapping to add code to the home page. I set it up using Pre-Build and Pre-Process to run before anything the homepage has runs. My code is basically looking for a condition and doing a transfer()...very similar to your code. However, the homepage is fully rendering and then transferring. Is there a reason it continues to process the homepage after I've done a transfer()?

Kishor Jammula said...


Please let me know the steps to create dynamic announcements/alerts based on job data change (PAR/action) to announcement pagelet to particular peoplesoft role.

i do create view and generate using EOAN appengine (run alerts) as scheduled but my requirement is as soon as job get changed alert should be sent to users.

Currently i am executing same process using peoplecode on JOB data to get notification
do we have any other alternative to achieve through setup online

Harika K said...

Hi Jim, Great Post. Thanks for sharing. We have requirement to add two new custom components to Personal Details tile and also to hide one of the delivered component (Form I-9). I am trying to achieve this using Event Mapping.

1) I tried to add custom component using the code posted in your blog. The title for my custom component appears twice.
2) Also, I tried to add map the field change event mentioned in this post. But I could not find the record field HCSC_FL_WRK.HCSC_BTN_SELECT. It is only showing up two fields GROUPBOX_04, GROUPBOX_05.
3) How can I use Event mapping to hide one of the delivered components on Personal details tile.

Thank you!!

Jim Marion said...

@Joe, I would not expect the behavior you are seeing. With Fluid, however, I have had a lot of problems with the Transfer function. I noticed a lot of delivered PeopleCode uses %Response.RedirectURL instead. I use one of the GenerateComponentxxxURL functions to create the URL, and then %Response.RedirectURL to perform the transfer. That has solved my issues with the Transfer function.

Jim Marion said...

@Harika, since the list of links on the left is a grid, you can hide a link by finding the row and using row.visible = false. The rows are numbered as you see them on the screen, so count and then use that as your index. I believe the rowset is GetLevel0()(1).GetRowset(Scroll.HCSC_TAB_DVW).

Regarding the second set of labels, I had the same problem and can't remember exactly what I hid. I had to inspect the HTML to find the issue. I don't remember if it was the title attribute or another field or label. I also don't remember if I used CSS to hide the second label or if I was able to accomplish it with field.visible = false.

Jim Marion said...

@Harika... I should say, I had the same two-label problem after upgrading to a later PUM.

Joe Mumbauer said...

Jim, I finally got around to trying your suggestion and what's interesting is that the GenerateComponentPortalURL still waits to load the homepage first! I would like to have the header on the page I'm transferring to, but I'll do without just so that the homepage doesn't load. Using the GenerateComponentContentURL function goes directly to my page without loading the homepage first. Thanks for the help!

Ritu said...

Hi Jim,

I am facing an issue. In component record level mapping, all the records which are present in the component are not showing up in the prompt. I was doing this for COLLECTION_WRKBNCH component ITEM_QRY_WRK record. But this record although is present in the component, is not showing up in the prompt.

Any suggestion would help.


Jim Marion said...

@Ritu, I have the same issue. Generally this relates to subpages and the delivered module not drilling into sub pages. Please log a support case with My Oracle Support. Yes, it is supposed to allow you to map to sub pages. (I say that just in case the My Oracle Support rep says it is working as designed).

Unknown said...

Hi Jim,
I saw your presentation on HEUG Alliance in which you used event mapping to display the pin icon by checking PS_PT_PORTAL_PINTO record. Can you please share the stylesheet that you used in the event mapping execute method?