Wednesday, July 19, 2017

Event Mapping: Extending "Personal Details" in HCM

As you would expect, PeopleSoft's HCM self-service functionality allows employees to self-report many industry-generic, best-practice attributes. But none of us are industry-generic, which means we may have to capture more attributes than Oracle intended. The way I've seen many organizations handle this is to customize the delivered Personal Details pages to collect additional attributes. Although having an upgrade impact, customizing the classic versions of these pages makes a lot of sense. With continuous delivery, however, customers no longer apply massive upgrades, but rather iterative, incremental continuous upates. With this in mind, the cost of maintaining a customization is significantly higher than the traditional periodic upgrade model. A customization may be the only thing standing between you and important new features. Wouldn't it be nice to revert your personal details pages to vanilla?

Classic HCM housed several components that collectively represent "Personal Details." The Fluid iteration of Personal Details uses a design pattern akin to a WorkCenter to colocate the navigation for each of the Personal Details components. Rather than customize delivered components, what if we took any custom attributes and placed them in a separate component and then added that separate component to the list of Personal Details components?

The Personal Details tile of an HCM Employee Self Service landing page is a link to the employee address component (HR_EE_ADDR_FL). This component (or rather the primary page in the component) uses a two-panel layout to display a list of links on the left and a transaction area on the right. With a little bit of App Designer investigation, we see that the list on the left is really a Derived/Work disconnected Rowset populated through PeopleCode. Therefore, to add a link to the left-hand list, we need to insert rows into that disconnected Rowset. The question is, "How do we add a row to this list without modifying delivered PeopleCode?" The answer: Event Mapping. Related Content Event Mapping is an 8.55 PeopleTools feature that lets a developer map a PeopleCode event handler into a component event. What this means is we can write PeopleCode separate from Oracle's delivered PeopleCode and then map our PeopleCode into the same events already handled by Oracle. Since we are not intermingling our code with Oracle's, this is a configuration, not a customization.

Event Mapping configuration requires the following steps:

  1. Create an Application Class with mapped business logic,
  2. Define a Related Content Service Definition, and
  3. Map a component event to a Related Content Service Definition.

Before writing any PeopleCode, I recommend identifying your target event. Your PeopleCode has full access to the component buffer and executes in the same context as the target event handler. If your event handler targets RowInit of level 2, for example, PeopleCode functions such as GetRowset and GetRow will return the level 2 rowset or row respectively. Another reason to identify your target event first is because it is a good idea to have an understanding of the event PeopleCode you will be supplementing.

Oracle populates the left-hand list using component PostBuild PeopleCode. PostBuild is a great place to populate a navigation rowset, so we might as well use the same event. To begin, I created an Application Package and Class named GH_PERS_DET_EVT and PersonalDetailsTravelPrefs respectively. Next, we need to add a bit of PeopleCode to populate the appropriate Derived/Work record fields and rows. Identifying the proper buffer references requires a little bit of investigation. The key here is that Event Mapping PeopleCode has full access to the component buffer just like any other PeopleCode executing from a component event. Here is my PeopleCode:

import PT_RCF:ServiceInterface;

class PersonalDetailsTravelPrefs implements PT_RCF:ServiceInterface
   method execute();
   
private
   method AddStyle(&infld As Field, &inStyleName As string);
end-class;

method execute
   /+ Extends/implements PT_RCF:ServiceInterface.execute +/
   
   Local Rowset &rsLinks = GetLevel0()(1).GetRowset(Scroll.HCSC_TAB_DVW);
   
   &rsLinks.InsertRow(&rsLinks.ActiveRowCount);
   
   Local number &linkNbr = &rsLinks.ActiveRowCount;
   Local Row &linkRow = &rsLinks.GetRow(&linkNbr);
   
   Local Record &recWrk = &linkRow.HCSC_FL_WRK;
   Local boolean &isAccessibleMode = False;
   
   &linkRow.HCSC_TAB_DVW.ROW_NUM.Value = &linkNbr;
   
   %This.AddStyle(&recWrk.HCSC_GROUPBOX_02, "psa_vtab");
   
   /* initially hide counter and subtabs */
   &recWrk.HCSC_COUNTER.Visible = False;
   &recWrk.HCSC_EXPAND_ICN.Visible = False;
   %This.AddStyle(&recWrk.HCSC_GROUPBOX_03, "psc_hidden");
   
   &recWrk.HCSC_BTN_SELECT.Label = "Travel Profile";
   &recWrk.HCSC_BTN_SELECT.HoverText = "Travel Profile";
   
   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 | "'";
   
   If GetUserOption("PPTL", "ACCESS") = "A" Then
      &isAccessibleMode = True;
   End-If;
   
   If Not &isAccessibleMode Then
      
      /* set label image */
      &recWrk.HCSC_BTN_SELECT.LabelImage = Image.PS_EX_EXPENSE_M_FL;
      %This.AddStyle(&recWrk.HCSC_BTN_SELECT, "hcsc_image-maxtabheight");
      %This.AddStyle(&recWrk.HCSC_GROUPBOX_02, "psc_list-has-icon");
      
   End-If;
   
end-method;

method AddStyle
   /+ &infld as Field, +/
   /+ &inStyleName as String +/
   
   Local array of string &arrClass;
   
   REM ** Don't add classes that already exist;
   &arrClass = Split(&infld.FreeFormStyleName, " ");
   
   If &arrClass.Find(&inStyleName) = 0 Then
      &infld.AddFFClass(&inStyleName);
   End-If;
   
end-method;

Most of the code is self explanatory. It inserts a row into a rowset, and then sets appropriate values for each of the necessary fields. I was able to identify the relevant fields by investigating how Oracle populates this rowset. There is one line, however, that differs dramatically from Oracle's delivered code, and that is the line that sets a value for HCSC_BTN_SELECT.JavaScriptEvents. The delivered design for this Rowset uses FieldChange PeopleCode to Transfer to a different component on click. If you are using PeopleTools 8.55, you do not have access to map a handler to the FieldChange event. Likewise, even though 8.56 has support for mapping to the FieldChange event, early releases, such as 8.56.01 and 8.56.02 do not support mapping to FieldChange events in subpages. This rowset happens to reside in a subpage. As an alternative, this code generates a URL to the target component and then sets the HTML href attribute of the inserted row so that clicking the link opens a new component.

Note: the transfer method described here may not display the usual PeopleSoft warning message regarding unsaved data. A future iteration would leverage the FieldChange event, but not until after Oracle posts a fix for components with subpages.

The next step is to define a Related Content Service Definition. Although not necessarily related, the Related Content Framework contains all of the hooks necessary to implement Event Mapping. With that in mind, Oracle chose to make Event Mapping a subset of the Related Content Framework. To define a Related Content Service Definition, navigate to PeopleTools > Portal > Related Content Service > Define Related Content Service and add a new value. The ID you choose for your Related Content Service is not important. No one except an administrator will see the ID. Enter a user friendly service name and choose a URL Type of Application Class. It is this one piece of Metadata that will tell the Event Mapping Framework what code to invoke. When the Application Class Parameters group box appears, enter your package, path, and class name.

The final step is to map the Service Definition into a component event. Navigate to PeopleTools > Portal > Related Content Service > Manage Related Content Service. When you first land on this page, you may see a list of Content References containing Related Content. Immediately switch to the Event Mapping tab. On this tab, you will see an inappropriately labeled link with the text Map the event of the Application pages. Select this link. PeopleSoft will respond by displaying the typical enterprise menu in a tree structure. Since we are mapping to a Fluid component, and Fluid components don't exist in the menu, check the Include hidden Crefs checkbox. This will make the Fluid Structure Content item visible. Expand Fluid Structure and Content > Employee Self Service and then select Personal Details. Upon selection, PeopleSoft will present you with the Event Mapping configuration page. Notice that this page is divided into sections, with each section denoting a different definition type. The first group box, for example, is for Component events. Since we are mapping to the Component PostBuild event, it is this first group box we need to configure. From the Event Name drop-down list, select PostBuild. Next, select the service you created in the previous step. Since I created a service named GH_PERS_DET_TRAVEL, that is the Service ID selected in the screenshot below. The final metadata attribute, Processing Sequence, is very important. This attribute defines whether our code should run before or after Oracle's delivered code. In this case we are adding rows to the end of a Rowset and we don't want Oracle to do anything that would change the appearance or behavior of the rows we add. With that in mind, we choose Post Process, which tells the framework to run our code AFTER Oracle's delivered code. Save and test.

The above screenshot is from PeopleTools 8.56. Depending on your tools release, your page may appear slightly different.

After configuration, you should see a screenshot that resembles the following. Note the Travel Profile link at the bottom of the list.

Note: As previously mentioned, the Personal Details component contains links to several other components. To ensure that your component appears in the list on each of these other components, you also have to map your PeopleCode into the PostBuild event on each of those other components. Since these other components do not exist as tiles, you will find them directly in the Fluid Pages folder.

Special shout out to my friend Mike at Sandia National Labs, who demonstrated a similar approach at Collaborate 2017. Thank you, Mike, for the encouragement to persevere. I initially wrote the code and configurations for this blog post in December while working with some friends in UK. Unfortunately, due to inconsistencies in PeopleTools at that time, this solution did not work. With PeopleTools 8.55.15 being incredibly stable, this solution is now fully functional. I initially gave up hope for Event Mapping in Fluid. But seeing Mike present the exact same scenario renewed my faith.

24 comments:

Banksy said...

It would be interesting to see how this stacks up to just customising this the 'pre event notification' way. I'd be tempting to argue it's possibly simpler to support.

Although having said that, it appears 'Personal Details' and 'Time' tiles within HCM ESS were built before Navigation Collections became the standard way to solve this problem of adding a custom component.

Jim Marion said...

@Banksy, the biggest problem with these components is that they don't use Navigation Collections. I wish they did. Yes, I believe they were created before Navigation Collection "Workcenters" for Fluid. But then I wonder if HCM would have used the simpler Navigation Collections because these components also have a header that wouldn't be possible with the Navigation Collection approach.

I agree, from an LCM perspective, I'm honestly not sure which way is easier. If a developer just modified the delivered code, they would only need to make one change, not map to every component in the collection. Later this week I plan to publish the FieldChange PeopleCode for the links. At that point, the PeopleCode looks even uglier.

Daniel Wandow said...

Hi Jim,
Do you know if Oracle has made the VirtualBox image available for HCM 9.2 with Tools 8.56?

Thanks,
Daniel

Jim Marion said...

@Daniel, as of 22, no. I'm hoping it will be in 23, which is scheduled to ship on Friday.

Unknown said...

Hi Jim,

Does the custom fluid page you have in the code has the same two panel structure like delivered page HR_EE_ADDR_FL with HCSC_MLIST_SBF sub page for links?
Is there a way we can add custom classic components to the personal details where we do not have two panel layouts?

Thanks,
Prasanth

Jim Marion said...

@Unknown, yes the added custom page is actually a clone of the delivered page, but with the content area changed for new data. It may be possible to add a Classic page, but that would be difficult. The delivered navigation collection/workcenter approach supports classic pages, but the two-panel approach is different.

Unknown said...

I Created an App package and class in App Designer.
When I go to Define Related Content Service, I can select Package but there is nothing
on the class Name .
What do I need to do to have the class show up?

Jim Marion said...

@Unknown, it sounds like you might need a Class Path. Did you use a sub package when you created your app class? If not, just use ":"

PeopleTools said...

Hi Jim,

So, from PT8.56.03 onwards the Component has changed for Personal Details to HC_HR_PDTL_LAUNCH_FL_GBL. Hence, your peoplecode not working with Event Mapping, so could you check and let us know the changes required.

Thanks

Jim Marion said...

@PeopleTools you are correct. They changed the starting component. I have an updated copy, but can't find it. From what I remember, the key difference was a change in the CREF mapping. The addresses component is now a hidden CREF under Fluid Structure and Content > Fluid Pages.

Satyajit said...

Hi Jim,

The back button issue(Back button click event is not working)- when i am opening a peoplesoft fluid approval page(FSCM 9.2) and in subsequent steps if i am opening a modal page in fluid, back button is not working.
The opening of modal page is happening like your example.

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 | "'";

In next step, LaunchUrl custom javascript method is used for opening the modal component.
It is a delivered issue.



Kevin Weaver said...

Jim,

Another great post! I am adding a way for employee's to update their driver's license using this component. Everything works like I expect, but I am missing the more icon? or the greater than icon ">" at the end of the actionable grid row that tells the user that the row actionable? I have added the following styles to my grid under the Fluid tab in the default styles: psc_show-actionable psc_grid-notitle

How do I get the More Icon to show?

Thanks!

Kevin

Jim Marion said...

@Kevin, I hope all is well?

There are a few steps to get the row actionable indicator to appear. Sasank provides all of the details in his post https://pe0ples0ft.blogspot.com/2019/01/fluid-ui-working-with-grids-part-2.html. Let me know if you still have questions after reading his post!

Unknown said...

Jim,
Great post ..I am able to see only a handful of records in the prompt after configuring personal_data conponent in hcm what about the rest,what about the others.I actually want to call a function in person.emplid save prechange,i am not able to see this event either as only fieldchange seems to be visible.Inputs will be appreciated on this

Jim Marion said...

@Unknown. FieldChange is the only field level event available. There is also a bug in several tools releases that won't allow you to select records from subpages. You are in luck, however, because SavePreChange is actually a component level event, so field-level events are irrelevant.

Unknown said...

hey Jim

we have this requirement where I need to add a custom classic component to Personal details tile, we are in 8.56.04, I tried to use your code but it does not seem to work.
Do you have an updated code for 8.56.04? Also, another thing that I notice is the code that I am providing, i think that is getting called before the delivered code can get called.. even though i use Post Process in Post Build? Any thoughts why?

Jim Marion said...

@Unknown, in 8.56 I switched to PageActivate. Otherwise, it has worked for me unchanged.

Unknown said...

Hi Jim,

Is it possible to use event mapping with secondary pages/modal windows? There's a lot of those inside the Profile collection (like Names, Addresses, Phones, etc).

Thanks,
Trey

Jim Marion said...

@Trey, great question. First, the entire component buffer is available to event mapping and secondary pages are part of the component buffer. Looking at it another way, what about PageActivate on secondary pages? I'm not sure if 8.55-56 will let you use secondary pages, but 8.57 should through the "Unrestricted prompt" checkbox.

Unknown said...

Jim - As a follow up to what I asked. I was able to alter secondary/sub pages through event mapping but there is something odd about the situation.

There is delivered code on the Component Page Activate event. I figured this would fire first and my event mapping would fire last, provided I selected the right spot to put it in (and chose Post Processing). But when I tried it in the Component Page Level Event Mapping POST process - the delivered active code always runs last which means my event mapping code is useless.

Is this a bug? Is this a limitation of event mapping? I am using pt85614

Thanks,
Trey

Jim Marion said...

That sounds like a bug. You are correct, Post Processing should trigger your code last.

Appsian said...

Thank you for sharing your blog, seems to be useful information can’t wait to dig deep!

Srikanth said...

Hey Jim - When I setup the related content service , on the side of the page it shows "Related Information" , is there a way to change this label?

Jim Marion said...

@Srikanth, there is not a good solution for that. It is not a configuration option, and the CSS selector path is quite generic.