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();
end-class;

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

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.

15 comments:

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: http://jjmpsj.blogspot.com/feeds/posts/default

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.

Neil

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...

@Tom,

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.
http://garretts-ps-tips.blogspot.com/2008/09/when-page-data-is-inconsistent-with.html


import PT_RCF:ServiceInterface;

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

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);
End-For;
End-For;
End-For;
end-method;

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
End-If;
&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);
End-If;
End-For;
End-If;
End-For;
End-For;
end-method;


Thanks...

Tom.

Jim Marion said...

@Tom, This is great! Thanks!

Pat O'Hare said...

Jim
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.
Thanks
Pat

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...

Jim
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
UPDATE PSPTCSSRVCONF SET RECNAME = 'SCC_CITIZEN_H'
WHERE PORTAL_NAME = '_PTCS_PTEVMAP'
AND PORTAL_OBJNAME = 'HC_SCC_ADD_PERSON_GBL'
AND PTCS_SERVICEID = 'GL0_CITIZENSHIP'
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.
Pat