Showing posts with label Event Mapping. Show all posts
Showing posts with label Event Mapping. Show all posts

Tuesday, February 04, 2025

Page and Field Configurator or Event Mapping?

Which tool would you use if someone asked you to hide a PeopleSoft field? We asked our LinkedIn audience that same question. Here are the results:



85% of respondents chose Page and Field Configurator, which is fantastic!

Both tools can hide a field, but is one a better option? I use a decision matrix to help me decide which one to use:

Decision Matrix

Event Mapping PaFC
Can the solution be configured with PaFC?
PaFC cannot provide the expected result.

If you can use Page and Field Configurator, then choose it for these two reasons:

  • Page and Field Configurator contains a "Validate Configuration" feature to verify the configuration is still relevant after applying maintenance and
  • Functional business analysts may manage Page and Field Configurator (Event Mapping is a developer-only solution).

When should you use Event Mapping? For everything else. For example, Page and Field Configurator does not yet work with Group Boxes. Likewise, complex logic may require Event Mapping (or might be easier to read and write if written in Event Mapping).

We teach both topics in our three-day Configure, don't Customize course. Check it out to see when we are offering it next!

Tuesday, August 13, 2024

Should you use the Override and/or Replace Features?

I absolutely love Isolated Customization strategies such as Event Mapping and App Engine Action Plugins. These strategies allow us to extend Oracle's delivered solution without touching Oracle-delivered code. The benefit is more efficient maintenance cycles because of fewer retrofits.

Both of these features, Event Mapping and App Engine Action Plugins, have one shortcoming: You may not use them to inject code into the middle of an Oracle-delivered code listing. Event Mapping and App Engine Action Plugins only allow code to be inserted before or after an entire listing. What if you need to inject code in the middle? Until recently, the only option was to continue modifying Oracle's delivered code.

Both Event Mapping and App Engine Action Plugins have a feature allowing a developer to replace or override Oracle's delivered code listing with your own. Customers looking for "clean" compare reports may be tempted to copy the Oracle-delivered code into their own code listing, modify it, and then replace/override Oracle's delivered code. The end result is a clean compare report and hopefully less work when applying maintenance.

But does it really work that way? Let's say you take this approach. You clone Oracle's delivered code into your own code listing. You modify your copy. You use Event Mapping or App Engine Action Plugins to replace Oracle's code. Time passes, and you apply maintenance. Since the compare reports show no customizations, you adopt Oracle's changes with no retrofits. You test the system, and because of your overrides, the system continues using your code, not Oracle's code. This is good, right? Isn't this what you want? MAYBE NOT! Oracle delivered maintenance for a reason. Did they patch a security vulnerability? Did they fix a bug? Did they alter the data model or business logic in a manner that renders your clone less effective?

If you use the override/replace in this manner, you have no way of knowing what Oracle "fixed." The only way to know that you are using the latest version of their code is to perform your own manual compare and retrofit outside of PeopleTools. And if you are going to do that, wouldn't it be easier to just modify Oracle-delivered anyway? You know what is worse? Since overridden and replaced code listings don't show on compare reports, your system may be fully patched and still suffer security vulnerabilities in your clones of Oracle-delivered code.

The Override/Replace features are fantastic for REPLACING Oracle's delivered code. In other words, if you don't want Oracle's code to run at all, then override or replace it.

Want to learn more about PeopleTools Event Mapping? Check out our one-day on-demand Event Mapping course! Or even better, subscribe and get a full year of access to the entire JSMpros on-demand PeopleTools training library!

Want to further reduce your maintenance workload? Replace customization analysis and review with PeopleSoft's Automated Testing Tool (PTF). Learn how through our two-day PTF course, which is available on-demand and live!

Thursday, April 25, 2024

HOWTO Override Fluid Component Event Handlers

Oracle's delivered Fluid components use an interesting pattern: App Class Event Handlers. This isn't required. It's just a design decision. Here is how it works: a Fluid page's Component PreBuild usually initializes a component-scoped App Class variable and every subsequent event delegates to a custom App Class method. If done properly, this design decision has the following potential benefits:

  • Reusable,
  • Unit testable,
  • Extensible, and
  • It eliminates the "Data Integrity Error" when making changes to Component-specific PeopleCode while a component transaction is in progress.

Unfortunately, to be reusable and testable, App Class code must be context-agnostic. That means it can't leverage component buffer-specific functions, such as GetLevel0, GetRow, and GetRowset; it can't use context-specific variables, such as %Component; and it can't use bare references, such as RECORD.FIELD references.

We discuss these design concepts regularly in our PeopleCode Application Classes two-day course, and we wrote about the extensibility idea in this blog post. In the blog post, we noted that Oracle would need to change the way they load their App Classes in Component PreBuild to make event handlers extensible. But do we need to wait? We came up with an idea that allows us to implement this idea now: we can use Event Mapping to replace Component PreBuild so we can load our own App Class. As long as our new App Class extends the Oracle-delivered App class, all other event PeopleCode will delegate properly. In other words, PeopleSoft will use our code in all other events. What's interesting about this idea is that it may allow you to apply just one Event Mapping service to a component rather than one per event.

As an example, let's extend Direct Deposit by subclassing (overriding) one of its App Classes. Components that apply the event delegation pattern instantiate App Classes in PreBuild. Within the PreBuild of the Direct Deposit component (PY_IC_DIR_DEP_FL), we see the App Class PY_DD_SELFSERVICE:Utilities. That class includes several important methods, including one appropriately named PageActivate. Our goal is to use the PageActivate event to hide the Pay Statement Print Options box. We will accomplish this goal by using Event Mapping to replace PY_DD_SELFSERVICE:Utilities with our own Utilities subclass. Here is the code for the Utilities subclass:

import PY_DD_SELFSERVICE:Utilities;

class CustomUtilities extends PY_DD_SELFSERVICE:Utilities;
   method pageactivate();
   REM ** Add more methods to override mor functionality;
end-class;

method pageactivate
   /+ Extends/implements PY_DD_SELFSERVICE:Utilities.pageactivate +/
   REM ** invoke original pageactivate method since we are just extending, not replacing;
   %Super.pageactivate();
   REM ** The following line doesn't work because later Oracle-delivered code overrides it;
   REM PY_IC_WRK2.PRINT_OPTN.Visible = False
   PY_IC_WRK2.PRINT_OPTN.AddFFClass("psc_hidden");
end-method;

The next step is to apply Event Mapping to override Component PreBuild. Here is our sample code:

import PT_RCF:ServiceInterface;
import PY_DD_SELFSERVICE:Utilities;
import TRN_PY_IC_DIR_DEP_FL_OVRD:CustomUtilities;

class PreBuild extends PT_RCF:ServiceInterface
   method execute();
end-class;

Component PY_DD_SELFSERVICE:Utilities &DDUltities;

method execute
   /+ Extends/implements PT_RCF:ServiceInterface.execute +/
   
   &DDUltities = create TRN_PY_IC_DIR_DEP_FL_OVRD:CustomUtilities();
end-method;



After applying Event Mapping, the delivered Direct Deposit component will now pass all Utilities requests through our subclass. The standard, delivered &DDUtilities.pageactivate call that is in the middle of the delivered PageActiviate PeopleCode will now invoke our pageactivate method instead.

Summary

This was an interesting academic exercise with some benefits over Event Mapping:

  • This approach allowed me to indirectly inject code into the middle of an Oracle-delivered code listing. The delivered PageActivate event invokes &DDUtilities.pageactivate in the middle. Event Mapping would have required my change to appear at the end or beginning, but not in the middle.
  • I only had to configure one Event Mapping, not one for each event I desired to extend.

I also found some challenges that this approach could not solve:

  • I wanted to run code at the end of PageActivate, not in the middle. My CustomUtilities code triggers too soon. As you can see from the first code listing, I run Oracle's code through %Super, and then mine. But the actual event code listing has more code that overrides my code. Event Mapping is the only way to make sure your code runs last.
  • I would like to mask the routing number within the rows of the Direct Deposit grid. I would use RowInit to apply this masking. The delivered Direct Deposit component does not have code in RowInit. I would, therefore, have to use Event Mapping to apply RowInit code.

This is a pattern I'm going to keep in my toolbox. For this scenario specifically, Event Mapping without overriding was a better solution. But there are times where subclassing a backing App Class may make more sense.

Are you interested in learning more about PeopleTools and PeopleCode? Check out our live virtual and on-demand courses. Or even better, subscribe and get access to all of our content for a full year!

Friday, February 24, 2023

Does Event Mapping Apply to Content References?

Help! My Event Mapping code isn't firing! Because this happens occasionally, I have a simple debugging process:

  1. Insert a "Hello World!" Message Box statement into my code. The point is to prove whether or not Event Mapping is properly configured. If I don't see the Message Box, then I know PeopleSoft ignored my code, and I may have a misconfiguration. If the MessageBox appears, then the configuration is correct, and the problem is in my code.
  2. If the Message Box does not appear, the next step is to confirm the correct Content Reference. We apply Event Mapping through Content References. Selecting the wrong Content Reference will keep the code from executing. This is an easy mistake to make because several Content References use the same label. Sometimes the solution is a bit of trial and error, testing various Content References until we find one that works. We can also query PeopleTools metadata to confirm we selected the correct Content Reference.

Those two steps usually identify any issues. But this time was different. First, my "Hello World" Message Box did not appear. Second, I confirmed I was using the correct Content Reference. I was puzzled. What could it be?

As I dug through the metadata, I found something interesting! There were two Content References pointing to the same menu/component combination! The point of a Content Reference is to generate a unique URL fragment. PeopleSoft Component Content References use the menu, component, and market to generate that unique URL fragment. Have you ever tried to create two content references that point to the same menu/component combination? It doesn't work. PeopleSoft won't let you save until you make the URL unique. An old trick that developers use is to add Additional Parameters. A simple "X=X" is usually enough. Or in this case, it was "Tile=Y." (Note: While there may be a reason to create a redundant Content Reference, a better approach is usually to use a Content Reference Link).

Actually, finding redundant Content References is commonplace in today's Fluid Portal Registry. Fluid's framework components, such as Activity Guides, Dashboards, and Homepages require creating Content References that all point to the same menu/component combination, with Additional Parameters identifying uniqueness. We have done extensive research on Event Mapping and found this is normally not an issue. What we found is that applying Event Mapping to any one of those redundant Content References results in Event Mapping applying to all of the Content References. The example we use in our Event Mapping course is applying Event Mapping to Tile Wizard's runtime component.

This is what puzzled me with today's scenario. I already knew there were multiple Content References pointing to the same Menu/Component combination. I've repeated this many times without issue. If I apply Event Mapping to one of those redundant Content References, all exhibit the same behavior. But this time, nothing happened. Or more appropriately, the system didn't do what I thought it should do. Upon further inspection, I found that someone else had used Page and Field Configurator to apply Event Mapping to the other Content Reference. It appears that PeopleSoft properly found Event Mapping for the menu/component combination, but the configuration it found wasn't mine. It seems that Event Mapping queries the database for the first configuration. Mine happened to be second.

I find it interesting that we configure Event Mapping through Content References, not Menu/Component combinations. Based on my experience today, and the fact that we must apply a Menu to a Component Interface to leverage Event Mapping against Component Interfaces, it seems that Event Mapping really applies to Menu/Component combinations, not Content References. What if the Event Mapping configuration asked us to select a Menu, and then gave us a list of Components attached to that menu? What if we configured Event Mapping through Menu/Component combinations, not Content References?

  • Redundant Content References would be irrelevant.
  • We wouldn't have to create Content References against hidden components simply to apply Event Mapping.
  • We wouldn't have to guess if we chose the proper Content Reference based on a label and a root folder.
  • We would know for sure that we selected the correct Menu/Component.

With that in mind, I created a new Idea in the PeopleSoft Idea Labs. If you like this idea, please upvote, share, and leave comments.

At JSMpros, we teach Event Mapping regularly. Check out our website to see what we are offering next!

Thursday, February 09, 2023

Did Event Mapping Break Your Update?

I love Event Mapping. But I have a concern:

The benefit of Event Mapping is that your customizations no longer appear on a compare report, and the problem with Event Mapping is that your customizations no longer appear on a comapre report.

I know what you are thinking: "Wait, didn't he just contradict himself?" Yes! Let me explain with a scenario:

Let's say you move a customization into Event Mapping. Later you apply an update. You run a compare report and see the beautiful "change/change" with no asterisks. Perfect! You have no customized code, and therefore nothing to retrofit. And then you test the upgraded system. And you find the system is broken. Since you have no customizations identified in the compare report, you should be fine, right? If this happened to me, my first thought would be that something is wrong with the update, and I would file a support ticket. But unfortunately, Oracle support can't replicate the issue. After escalation and further analysis, Oracle discovers that custom Event Mapping is causing the problem.

I share this scenario because it is possible, but it seems like a worst-case scenario. Does it really happen? Do custom, invisible event mapping "configurations" ever break an update/get current? It turns out they do! MOS doc 2798164.1 was posted in 2021 and demonstrates this scenario.

The problem with broken Event Mapping code is that it fails just like any other code, so we can't tell that the failure was caused by Event Mapping. Event Mapping is not a configuration. It is an isolated customization.

Event Mapping is amazing! But until Oracle provides us with LCM tools that identify potential Event Mapping issues, we must perform our own analysis. Here are some options to help you catch troublesome Event Mapping:

  1. Use SQL in this blog post to create your own Event Mapping analysis.
  2. Use PTF to create Event Mapping and Page and Field Configurator Regression tests.
  3. Wrap Event Mapping in a try/catch block to log and notify.
We teach Event Mapping and PeopleSoft Test Framework regularly. Check out our website to see what we are offering next!

Monday, February 12, 2018

Alliance Event Mapping Stop and Share

Dave Sexton and I will be hosting a Stop and Share at Alliance 2018. Our primary subject is Event Mapping and Page and Field Configurator. We will be discussing:

  • Use cases,
  • Configurations,
  • Potential concerns, and
  • Lifecycle management

Please stop by Tuesday from 9:15 AM to 9:45 AM and share your experiences with Event Mapping and Page and Field Configurator or listen to experiences from others. I will bring a demo along with several use cases to spark discussion.

Tuesday, January 16, 2018

Disable or Hide a Radio Button Instance

I ran across a few blogs and forum posts from people either asking or sharing how to hide or disable radio buttons. The answers I saw appeared to address only part of the story so I thought I would share a solution. PeopleCode includes functions, properties, and methods for disabling and hiding fields. As you would imagine, A field property such as Visible will show or hide a field. What makes a radio button challenging is that a radio button represents several values of the same field. The Visible property applied to a radio button's field would hide the entire radio set, not just a single radio button. Likewise, disabling a radio button's field would disable the entire radio set. What we require is a reference to one instance of a fieldset, not the base field itself.

PeopleCode includes two functions that return a reference to a field: GetField and GetPageField. The first, GetField, returns the same field reference as a standard Record.Field reference or Rowset.GetRecord.GetField. The GetPageField function, on the other hand, returns a pointer to a single instance. While this might seem like the answer, it is only part of the story. The GetPageField function does return a single instance and setting some of the properties of this single instance only manipulates the single instance. Other methods and properties, however, appear to be tied to the base field. Unfortunately, the Visible and DisplayOnly properties are two properties bound to the underlying field. Changing either of these on a GetPageField reference will hide or disable the entire radio set, not just a single instance.

The solutions I have seen, and the one I recommend, is to use CSS and/or JavaScript to hide or disable a radio button. Here is an example in Fluid:


   Local Field &compBtn = GetPageField(Page.HR_DIRTEAM_FLU, "COMPENSATION_BTN");

   rem ** hide a radio button instance;
   &compBtn.AddFFClass("psc_force-hidden");

   rem ** or disable a radio button instance;
   &compBtn.AddFFClass("psc_disabled");

From a visual perspective, you are done. You have successfully completed your mission. Unfortunately, however, this is only part of the answer. An important part, but only part. Hidden or disabled HTML still exists. That means I can use a standard browser tool, such as Chrome inspector or IE Developer Tools to show or enable this HTML. In fact, even if the HTML elements didn't exist, I could still invoke JavaScript to make the app server think I had selected the radio button.

The only way to ensure the server never receives the value identified by the hidden or disabled radio button is to either use FieldEdit PeopleCode or Event Mapping FieldChange Pre Processing to change the value before delivered PeopleCode ever sees that value. This is part two. This is the part that seems to be missing from other solutions I have seen.

What got me thinking about this? The Fluid My Team page contains a button bar that allows a manager to switch between alternate views. One of the radio buttons in the button bar is Compensation. Some organizations do not want managers to see compensation. My challenge was to remove the compensation radio button in a secure manner without customizing delivered definitions. Using Event Mapping on PageActivate I was able to hide the Compensation button. Event Mapping FieldChange PeopleCode ensures PeopleSoft never triggers FieldChange for the compensation button.

Friday, December 29, 2017

January PeopleTools Training Courses Posted

We posted our January PeopleTools training agenda. This month includes our extremely popular Fluid course as well as our new PeopleTools Delta course. Register online at www.jsmpros.com/training-live-virtual/. Our First course is January 8th, which is coming up quickly.

Besides our regular mid-week agenda, we added weekend courses for those that are committed mid-week and can't take time away for training.

The time zone for these courses is designed to catch as many US attendees as possible. If you would prefer another time zone, let me know and we will consider scheduling a future course in a more favorable time zone.

Why Fluid and why PeopleTools Delta? Fluid first: Any PeopleSoft 2017 Year in review post must include Fluid Campus Solutions. Oracle's Campus Solutions team made significant progress in Fluid student self-service. Honestly, I am extremely impressed with Fluid student self-service. Because of this progress, many of our customers are currently implementing Fluid student self-service. Likewise, PeopleSoft HCM has published retirement dates for key manager and employee self-service components. Support for most Classic manager self-service components, for example, retires in just a couple of days. Classic employee self-service retires one year later, on December 31, 2018 (for more details on Classic retirement dates, see MyOracle Support document 1348959.1). If there was ever a time to think about Fluid, that time has come. Now is a great time to learn Fluid so that you are ready for those change requests and implementation details. While there are obvious similarities between Classic and Fluid development, they are very different.

As customers implement Fluid, they will undoubtedly revisit existing customizations. This is where a PeopleTools Delta course becomes important. You could continue down the same path, customizing delivered definitions or you could investigate new PeopleTools features that allow you to configure (instead of customize) business logic and tailor the user experience. I could recount story after story of customers saving 10's to 100's of thousands of dollars in implementation, customization, and lifecycle management costs because they learned new PeopleTools features.

Does your organization have 8 or more people interested in a training course? If not, do you know 8 or more people from various organizations you can get together for a class (with virtual training, all users can be remote)? If so, we have group rates available. Besides significant savings (quantity discounts), large groups have flexibility and control over the training agenda. Feel free to contact us for more information.

Wednesday, December 27, 2017

Event Mapping Lifecycle Management (LCM) Tools

I am a big fan of Event Mapping. Without question, if presented with a modification opportunity, my first choice is Event Mapping. In a nutshell, the goal of Event Mapping is a clean compare report. Event Mapping allows us to move PeopleCode modifications out of delivered PeopleCode and into custom Application Classes. We then use the Event Mapping framework to configure our PeopleCode into delivered events. Since our custom PeopleCode is not part of delivered event PeopleCode, it won't show in a compare report. At first, this might seem like a great idea. But let me tell you a story. The story you are about to hear is true. I didn't even change the names to protect the innocent.

I previously wrote about Event Mapping: Extending "Personal Details" in HCM. In that blog post, I showed how to use Event Mapping to add links to the left-side panel of the Personal Details component, the panel containing navigation. Several months later I applied PUM 24. Sometime between my release of HCM and PUM 24, Oracle updated the code behind the Personal Details components. Because my compare report was clean, I falsely believe there would be no issues. Post update, I noticed that my Travel Preferences link, the link added by my prior blog post, appeared twice. Obviously something was broken. But what? Why didn't my LCM tools catch this? I followed all of the best practices, including using Event Mapping. My compare report was clean.

After some investigation, I found that Oracle changed the component buffer and PeopleCode. In fact, while digging through the code, I found a reference to bug 25989079 with the resolution: Menus In Employee Self Service Fluid BUTTONS/LINKS WITH IMAGE AND TEXT GET READ TWICE (Doc ID 2253113.1). After a minor copy/paste exercise and some cleanup, my configuration is working again. No modifications to delivered objects.

This incident causes me to pause and ask some questions:

  • Is Event Mapping a Best Practice?
  • Would a compare report have identified the issue and changed code much faster?

I'm going to start with the second question first. When we are talking about the simple, event-based PeopleCode of the 20th Century, yes, a compare report would have located the differences among a few thousand lines in a single event. Today, however, a self-service component consists of thousands of lines of PeopleCode spread over several dozen App Classes. In this environment, your code change may be in one method whereas the bug fix is in another, related method or App Class. That was exactly the case for me. The code that inserts rows into the left-hand list is quite separated from the row that hides the extra hyperlink. A compare report would have found my modifications, but would not have shown Oracle's changes.

Is Event Mapping a best practice? YES! ABSOLUTELY! Without Event Mapping, our code is overwritten and we have to figure out how to merge it back into the delivered code base. With Event Mapping, it is more like an overlay, where our code still exists and lays over the top of Oracle's code. No reapplication necessary. When it works, it works great!

As noted, Event Mapping doesn't eliminate Lifecycle Management issue, but reduces them. What we are lacking today is tools to help us manage this new LCM "wrinkle." I'm hopeful that Oracle will deliver targeted tools in the future. But why wait? If PeopleSoft is metadata driven, why not create our own tools? The following is an SQL statement I put together to help me identify components that use Event Mapping. Here is how it works... Let's say you have components with Event Mapping. Let's also say you are about to apply a change project generated from a PUM image. That change project may contain components. The SQL below will tell you if any of the components in your project contain Event Mapping configurations including which components, events, and App Classes.

Caveats:

  • This SQL was written for PeopleTools 8.56, and therefore may contain fields that don't exist in other PeopleTools releases (such as 8.55).
  • The SQL contains SYSDATE, which is Oracle specific.

The following is a screenshot of my Personal Details "component" (component in air quotes, because Personal Details is really a collection of components). In that screenshot, you will see the following changes:

  • Page title changed to reflect the active component
  • Navigation list contains additional elements
  • Address group headers contain icons

Inserting a row into the left-hand navigation required RowInit PeopleCode. Clicking a link in the left-hand navigation invokes FieldChange. Changing the title and adding icons involved PageActivate PeopleCode. These are 3 distinct events at 3 different levels. I added the Personal Details Addresses component as well as several other Event Mapping configurations to a project. The following is a screenshot of that project:

When I run this SQL, I see the following output:

In the SQL results, did you see the EOCC_POSTBUILD Service ID? That is event mapping inserted by Page Field Configurator. Page Field Configurator is a tool on top of Event Mapping, and therefore suffers from the same LCM concerns.

Do you want to learn more about Event Mapping? Event Mapping is a PeopleTools 8.55 new feature that is extended in PeopleTools 8.56. At JSMPROS, we regularly lead PeopleTools delta courses covering new features, including Event Mapping. Visit us at jsmpros.com/training-live-virtual to find a course that fits your schedule.

P.S. I will be sharing this example and many more in an HIUG webinar on February 16, 2018. If you are a member of the Healthcare Industry User Group, you won't want to miss this webinar!

Thursday, August 03, 2017

Event Mapping: Fluid Landing Page "Dot" Buttons

The bottom of a Fluid landing page contains dots that identify how many landing pages the user can view as well as the position of the current landing page within the user's landing page collection. This piece of information is quite useful on a swipe-enabled mobile device, but somewhat meaningless on my mac. A few months ago I was helping a PeopleSoft customer with their Fluid implementation. This rather brilliant customer made those dots clickable to navigate between landing pages. To implement this behavior, the customer modified delivered PeopleTools JavaScript and CSS definitions, which, unfortunately, presents a bit of an upgrade problem. In a recent webinar I mentioned that one of the things that excites me about Fluid is that everything is a component. What that means is we can use Event Mapping anywhere. Landing Pages are no exception. With that in mind, I wondered what this solution would look like with Event Mapping.

Those little dots at the bottom of the page are created through JavaScript. To enhance them, therefore, we will need to write some JavaScript. Because the original implementation of this behavior modified delivered PeopleTools JavaScript, the author was able to leverage existing JavaScript variables and functions. To avoid customizing PeopleTools, we will need to extract the relevant JavaScript and rewrite it in a standalone manner, meaning no dependencies on PeopleTools JavaScript. We'll start with a CSS selector to identify dots: .lpTabIndicators .dot. We will iterate over that dot collection, adding a click event handler to each dot. That click event handler needs to know the target tab on click so we need another CSS selector to identify tabs: .lpTabId span.ps_box-value. For compatibility reasons, I'm not going to use any libraries (like jQuery), just raw JavaScript:

(function () {
    "use strict";
    // include protection just in case PS inserts this JS twice
    if (!window.jm_config_dots) {
        window.jm_config_dots = true;
        var originalSetTabIndicators = window.setTabIndicators;

        // MonkeyPatch setTabIndicators because we want to run some code each
        // time PeopleSoft invokes setTabIndicators
        window.setTabIndicators = function () {

            // JavaScript magic to invoke original setTabIndicators w/o knowing
            // anything about the call signature
            var returnVal = originalSetTabIndicators.apply(this, arguments);

            // The important stuff that adds a click handler to the dots. This
            // is the code we want to run each time PS invokes setTabIndicators
            var dots = document.querySelectorAll('.lpTabIndicators .dot'),
                tabs = document.querySelectorAll('.lpTabId span.ps_box-value'),
                titles = document.querySelectorAll('.lpnameedit input');

            [].forEach.call(dots, function (d, idx) {
                d.setAttribute('title',titles[idx].value);
                d.addEventListener("click", function () {
                    lpSwipeToTabFromDD(tabs[idx].innerHTML);
                });
            });

            return returnVal;
        };
    }
}());

Funny... didn't I say we would start with a CSS selector? Looking at that code listing, that CSS selector and the corresponding iterator don't appear until nearly halfway through the listing. When testing and mocking up a solution, the objects you want to manipulate are a good place to start. We then surround that starting point with other code to polish the solution.

This code is short, but has some interesting twists, so let me break it down:

  • First, I wrapped the entire JavaScript routine in a self-executing anonymous JavaScript function. To make this functionality work, I have to maintain a variable, but don't want to pollute the global namespace. The self-executing anonymous function makes for a nice closure to keep everything secret.
  • Next, I have C-style include protection to ensure this file is only processed once. I don't know this is necessary. I suspect not because we are going to add this file using an event that only triggers once per component load, but it doesn't hurt.
  • The very next line is why I have a closure: originalSetTabIndicators. Our code needs to execute every time PeopleSoft invokes the delivered JavaScript setTabIndicators function. What better way to find out when that happens than to MonkeyPatch setTabIndicators? setTabIndicators creates those little dots. If our code runs before those dots are created, then we won't have anything to enhance. Likewise, when those dots change, we want our code to be rerun.
  • And, finally, redefine setTabIndicators. Our version is heavily dependent on the original, so first thing we want to do is invoke the original. As I stated, the original creates those dots, so it is important that we let it run first. Then we can go about enhancing those dots, such as adding a title and a click handler.

In Application Designer (or online in Branding Objects), create a new HTML (JavaScript) definition with the above JavaScript. I named mine JM_CLICK_DOTS_JS. You are free to use whatever name you prefer. I point it out because later we will reference this name from an Application Class, and knowing the name will be quite useful.

If we stop our UX improvements here, the dots will become clickable buttons, but users will have no visual indicator. It would be nice if the mouse pointer became a pointer or hand to identify the dot as a clickable region. We will use CSS for this. Create a new Free formed stylesheet for the following CSS. I named mine JM_CLICK_DOTS.

.lpTabIndicators .dot:hover,
.lpTabIndicators .dot.on:hover {
    cursor: pointer;
}

With our supporting definitions created, we can move onto Event Mapping. I detailed all of the steps for Event Mapping in my post Event Mapping: Extending "Personal Details" in HCM. According to that blog post, our first step is to create an App Class as an event handler. With that in mind, I added the following PeopleCode to a class named ClickDotsConfig in an Application Class appropriately named JM_CLICK_DOTS (noticing a pattern?). Basically, the code just inserts our new definitions into the page assembly process so the browser can find and interpret them.

import PT_RCF:ServiceInterface;

class ClickDotsConfig implements PT_RCF:ServiceInterface
   method execute();
end-class;

method execute
   /+ Extends/implements PT_RCF:ServiceInterface.execute +/

   AddStyleSheet(StyleSheet.JM_CLICK_DOTS);
   AddJavaScript(HTML.JM_CLICK_DOTS_JS);
end-method;

After creating a related content service definition for our Application Class, the only trick is choosing the Landing Page component content reference, which is a hidden content reference located at Fluid Structure Content > Fluid PeopleTools > Fluid PeopleTools Framework > Fluid Homepage

Probably the trickiest part of this whole process was identifying which event to use. For me the choice was between PageActivate and PostBuild. The most reliable way I know to identify the appropriate event is to investigate some of the delivered component PeopleCode. What I found was that the same code that creates the dots, PTNUI_MENU_JS, is added in PostBuild. Considering that our code MonkeyPatches that code, it is important that our code run after that code exists, which means I chose PostBuild Post processing.

Using Event Mapping we have effectively transformed a customization into a configuration. The point of reducing customizations is to simplify lifecycle management. We now have two fewer items on our compare reports. If Oracle changes PTNUI_MENU_JS and PTNUI_LANDING_CSS, the originally modified definitions, these items will no longer show on a compare report. But!, and this is significant: you may have noticed my JavaScript is invoking and manipulating delivered PeopleSoft JavaScript functions: setTabIndicators and lpSwipeToTabFromDD. If Oracle changes those functions, then this code may break. A great example of Oracle changing JavaScript functions was their move from net.ContentLoader to net2.ContentLoader. I absolutely LOVE Event Mapping, but we can't ignore Lifecycle Management. When implementing a change like this, be sure to fully document Oracle changes that may break your code. This solution is heavily dependent on Oracle's code but no Lifecycle Management tool will identify this dependency.

Question: Jim, why did you prefix setTabIndicators with window as in window.setTabIndicators? Answer: I am a big fan of JSLint/JSHint and want clean reports. It is easier to tell a code quality tool I'm running in a browser than to define all of the globals I expect from PeopleSoft. All global functions and variables are really just properties of the Window object. The window variable is a browser-defined global that a good code quality tester will recognize. The window prefix isn't required, but makes for a nice code quality report.

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.

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.