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!