Jim Marion

Subscribe to Jim Marion feed
A blog containing development tips I have learned through the years as a PeopleSoft developer.Jim Marionhttp://www.blogger.com/profile/12995110203807924786noreply@blogger.comBlogger196125
Updated: 9 hours 1 min ago

New Fluid Training Classes, Live and Virtual

Thu, 2017-09-07 04:16

On December 31, 2017, just a couple of months from now, Oracle will decommission the first set of Classic pages to be fully replaced by Fluid. Support document 1348959.1 contains a list of Classic pages, sorted by date, that are slated for retirement every year over the next few years. Customers interested in retaining a supported PeopleSoft implementation should familiarize themselves with this document and prepare to implement Fluid. This document contains important Manager and Employee self-service features that will no longer be supported in Classic form. Is your team trained and ready to adopt Fluid?

During my tenure at GreyHeller, I had the opportunity to lead several customer-specific Fluid workshops. The purpose of these workshops is to give customers hands-on experience with Fluid. These workshops are a starting point, a Fluid experience. As humans, we are often afraid of the unknown and these workshops provide practical, real-world experience.

I have expanded these workshops into a multi-day two-part series and am offering them in two formats:

  • Remote, but live virtual classes (LVC) hosted by GoToTraining or
  • By request on-site live instructor-led training.

Both courses (and formats) are two full days. The first course, Fluid 1, is a course designed for developers, architects, designers, and system analysts, and focuses on the basics of Fluid development. The point is to make students productive as fast as possible. Through hands-on activities, students will learn the basics of Fluid development including Fluid user interface configurations and best practices for page development. There is no extra fluff in this course. Students will learn how to build Fluid pages without having to become web development experts.

The second course, Fluid 2, teaches developers the techniques used by Oracle and modern web developers to build both responsive and adaptive user experiences through Fluid. This course is definitely level 2. Students will learn advanced topics such as how to use modern CSS frameworks for responsive design and how to create dynamic tiles for intelligent homepages.

Prerequisites? Experience. The entry requirements for Fluid 1 are intentionally low. The first half, day one, will be in the browser. No PeopleTools experience is required to use a browser. System analysts will benefit greatly from this half of the course. The second half, day two, is exclusively in Application Designer. In day two, students will build Fluid pages using Application Designer. Experience building Classic pages, records, and components is required. I have found there are a lot of non-developers with experience building and examining Classic pages, records, and components. That qualifies.

Students attending Fluid 2 are expected to have experience building Classic and Fluid pages in PeopleTools as well as experience working with Records, Fields, and PeopleCode.

Normally PeopleTools 1, PeopleTools 2, and PeopleCode would be prerequisites for learning Fluid. This is true. Anyone learning Fluid should have this experience. But often I find that people with relevant experience avoid registering for courses because they haven’t checked a specific training requirement box. Do not discount your on the job training experience.

I have scheduled two multi-day events and I encourage you to register as soon as possible. To ensure a quality experience, class size is limited. There has never been a more important time to learn Fluid!

Are you interested in hosting on-site training for your organization? For more information on these or other courses, please feel free to send a request to info@jsmpros.com.

Here are the full course abstracts:

Fluid 1Technology Overview
  • Why Fluid? Learn the history and purpose of Fluid
Hands on: Reviewing elements of Fluid
  • Fluid banner
  • Navigation bar
  • Fluid homepages (also known as Landing pages)
  • Tiles
  • Transaction pages
Working with Fluid Homepages and Dashboards
  • Understanding Fluid homepages
  • Hands on: Personalizing Fluid homepages
  • Hands on: Creating and managing personal and public Fluid homepages
  • Hands on: Managing Fluid system settings
  • Hands on: Creating and managing Fluid dashboards
  • Understanding the role of the Portal Registry in Fluid homepages
Working with Tiles and Tile Wizard
  • Understanding Tiles and the Tile Repository
  • Hands on: Creating tiles
  • Hands on: Managing Fluid Attributes of Content References
  • Hands on: Creating tiles with the Tile Wizard
  • Understanding the role of the Portal Registry in Fluid tiles
Creating Fluid Pages
  • Understand the differences between Classic and Fluid
  • Learn key design concepts such as Phone-first, Responsive, and Adaptive design and how they relate to Fluid
  • Hands on: Creating Fluid page definitions (standard and two-column)
  • Hands on: Using page and field Fluid-specific attributes
Working with Fluid Components
  • Understanding Fluid components
  • Hands on: Creating Fluid components
  • Hands on: Configuring Fluid Content References
Working with Search Pages
  • Understand Fluid search options
  • Hands on: Implement real-time Component search through Pivot Grids
  • Hands on: Implement real-time Component search through custom search Pages
  • Hands on: Implement keyword Component search through the Search Framework

Fluid 2Advanced Fluid Page Design
  • Understand the role of CSS in Fluid and responsive design
  • Hands on: Working with different Fluid page types
  • Hands on: Using Oracle-delivered Fluid CSS classes
  • Understanding CSS Flexbox
  • Hands on: Creating custom CSS classes
  • Hands on: Using CSS frameworks
  • Hands on: Using Fluid adaptive design with subpages
Using PeopleCode in Fluid Applications
  • Learn about and use Fluid-specific PeopleCode functions, design patterns, and best practices
Advanced Tiles and Tile Wizard
  • Hands on: Creating static and dynamic tiles using a variety of technologies
  • Hands on: Dynamic tiles and the Tile Wizard
Understanding Fluid Definitions and Metadata
  • Understand delivered Fluid managed definitions, the building blocks of Fluid
  • Understand Fluid metadata such as Tile Wizard metadata
Event Mapping in Fluid
  • Hands on: Learn how to extend delivered Fluid components without changing Oracle’s code

Event Mapping: Fluid Landing Page "Dot" Buttons

Thu, 2017-08-03 23:12

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.

Event Mapping: FieldChange

Fri, 2017-07-28 00:00

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.

Event Mapping: Extending "Personal Details" in HCM

Wed, 2017-07-19 09:27

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.

New Window Bookmarklet

Wed, 2017-06-21 12:05

I am a "New Window" link junkie. I use that link ALL THE TIME! If it were possible to wear it out, mine would be worn out. I wish all PeopleSoft pages had the "New Window" link. For some reason, however, certain developers chose to remove it from specific PeopleSoft pages (such as Structure and Content). I'm sure there is a good reason... there just has to be. So seeing it missing from Fluid has been a significant struggle for me. I'm thankful for Sasank's Fluid UI - New Window Feature - Workaround customization. For quick access to a new window without customization, I have a Bookmarklet, which is a JavaScript fragment masquerading as a favorite (or bookmark). Here is the JavaScript:

(function() {
var parts = window.location.href.match(/(.+?\/ps[pc])\/(.+?)(?:_\d+?)*?\/(.*)/);
window.open(parts[1] + '/' + parts[2] + '_newwin/' + parts[3], '_blank');
}())

To add it to your bookmark toolbar, drag the following link into your link toolbar:

PS New Window

This solution is simple, but may not satisfy your requirements. This bookmarklet assumes you want to open a new window to the URL displayed in the address bar. That URL may or may not match the actual transaction. If you want a bookmarklet that opens a new window specifically targeting the current transaction, then try this bookmarklet:

(function() {
var href = window.location.href;
var parts = (!!frames["TargetContent"] ? !!frames["TargetContent"].strCurrUrl ? frames["TargetContent"].strCurrUrl : href : href).match(/(.+?\/ps[pc])\/(.+?)(?:_\d+?)*?\/(.*)/);
window.open(parts[1] + '/' + parts[2] + '_newwin/' + parts[3], '_blank');
}())

To use it, drag the following link into your bookmark toolbar:

PS New Window

Special shout out to David Wiggins, who posted a similar bookmarklet on my Where is My New Window Link? post as I was writing this blog post.

Where is My New Window Link?

Tue, 2017-06-20 23:51

As PeopleSoft moves from Classic to Fluid, you have likely noticed the missing New Window link. Why is it missing? I can only speculate. When considering mobile, perhaps it makes sense to drop the New Window link. Mobile devices have limited screen real estate. Why waste it with a link you will likely never use on a mobile device? On a desktop, however, the New Window link is irreplaceable. So... what to do? How can you open a new window? You probably already know if you just open a new window without that special New Window link, your old window session will cease to exist. You know that you will receive the dreaded "... return to most recent active page..." message. Does that mean you can no longer have two active PeopleSoft windows? There is a work around that is documented in various places around the web. In short, the answer is to copy the current URL, open a new tab, paste the URL into the address bar, and then append _newwin to the site name. Before reviewing some examples, let's discuss what is going on and why this is necessary.

The PeopleSoft app server is stateless. App server connections are often pooled and used upon request. However, we know that component buffer state is stored somewhere. If not at the app server, then where? At the web server. As with any J2EE application, PeopleSoft uses web server sessions to store state (which is why load balancers must use sticky sessions, etc). The details here aren't exact, but metaphorical. PeopleSoft partitions web server session state into state blocks. A user may have multiple state blocks. The web server identifies the appropriate state block based on an identifier in the URL. When you click the New Window link, the link's URL pattern instructs the web server to generate a new state block. We can replicate the New Window link behavior by simply modifying a PeopleSoft URL. Let's review an example. Let's say you have a Fluid URL that looks something like this: http://hr.example.com/psc/ps/EMPLOYEE/HRMS/c/EL_EMPLOYEE_FL.HR_EE_ADDR_FL.GBL. The highlighted part between the servlet (psc) and the portal (EMPLOYEE) is the site name (ps). All we have to do is add _newwin to the site name. Accessing a URL such as http://hr.example.com/psc/ps_newwin/EMPLOYEE/HRMS/c/EL_EMPLOYEE_FL.HR_EE_ADDR_FL.GBL will instruct the web server to generate a new state block, perhaps something like http://hr.example.com/psc/ps_23/EMPLOYEE/HRMS/c/EL_EMPLOYEE_FL.HR_EE_ADDR_FL.GBL.

It certainly isn't as simple as the New Window link, but it is better than nothing. For a more permanent, user friendly method, take a look at Sasank's post Fluid UI - New Window Feature - Workaround.

Edit: I created this post because a lot of people I meet aren't familiar with the "New Window" trick. One very important caveat when working with classic pages: The URL in the header may not match the transaction URL. This appears to be tools release dependent. After PeopleSoft implemented "Partial Page Rendering" with a constant header and iframed content, the URL for the content area would change, but the URL in the address bar did not. What this means is simply copying the URL from the address bar and changing it to include '_newwin' will create a new state block, but that new window may point to different component from the original window.

HIUG Interact 2017

Thu, 2017-06-15 16:11

Are you attending Interact 2017 this weekend? I will be leading a PeopleTools hands-on workshop on Sunday from 1:30 PM to 4:30 PM in Panzacola F-4. Because this is hands-on, please bring your laptop. All session activities will be completed over a remote desktop connection, so as long as you have a Remote Desktop Client and a full battery charge, you should be ready. In this session we will cover a variety of topics including classic global and component branding as well as fluid navigation and page development. I look forward to seeing you in Orlando!

Using CSS Frameworks with Fluid

Tue, 2017-05-23 23:35

Unlike PeopleTools Classic, which used almost-pixel-perfect layout, Fluid uses CSS for layout. The benefit of this pattern is flexibility. Unlike Classic pages, which only allowed for one layout, Fluid allows us to manipulate layout with CSS. I like CSS, so I see this as a positive. The more content Oracle development delivers in CSS, the more we can configure and transform the user experience. While I may love what I can do with CSS, it isn't the easiest language/technology to use. As a PeopleSoft developer with a significant amount of front-end development experience, I had this thought:

Could PeopleSoft developers avoid learning and writing CSS by using common, modern CSS frameworks to enhance PeopleSoft pages?

As with anything, the answer is, "Yes," but it isn't that easy. Here is the reason: PeopleSoft stylesheets apply style rules to HTML elements. And this makes sense. Review the most common CSS frameworks and you will see that they do the same thing (honestly, I would despise a CSS framework that didn't apply rules to HTML elements, but that is another story). Here is the problem: since both Fluid and and the framework style HTML elements, there are bound to be conflicts, and those conflicts often cause layout and display problems. Normally this would be fine, because selector specificity and order dictate who wins. The real problem is where the two frameworks have different, but related CSS declarations. The solution? A reset stylesheet. A reset Stylesheet will either reset conflicting Fluid CSS properties or conflicting your-favorite-CSS-framework properties. Now we have a solution. But, this solution has a serious problem: the point of using a CSS framework was to avoid writing CSS. After working through all of these conflicts, it is highly possible that you will write more CSS than you would have without the CSS framework. CSS frameworks are highly powerful and have value, but be careful to pick a CSS framework that uses class selectors only; no element selectors.

I have reviewed many CSS frameworks and it is hard to find one that just uses classes, no element styling. Oracle JET is a CSS framework that supports class-only styling. Oracle JET's Alta CSS stylesheets come in two flavors:

  • HTML element selectors
  • Class-only selectors

To incorporate Oracle JET with PeopleSoft Fluid:

  1. Download the latest class-only oj-alta-notag-min.css file (you can download the uncompressed version if you prefer). It is important that you grab the *-notag-*.css version. This is the one that uses class name selectors rather than HTML element selectors.
  2. Create a new Stylesheet definition and paste the contents of the oj-alta-notag CSS file into the new Stylesheet definition.
  3. Use the AddStylesheet PeopleCode function in a Page Activate event to add the new free form stylesheet to a page.
  4. Add Oracle JET class names to fields on your page.
1. Download the latest oj-alta-notag-min.css file

You don't need to "download" the file, just access its content. We will copy and paste the content into PeopleSoft in the next step. The file is available in raw form on GitHub here.

2. Create a new Stylesheet definition

After logging into your PeopleSoft development instance as an administrator, navigate to PeopleTools > Portal > Branding > Branding Objects. Switch to the Stylesheet tab and select the Upload Style Sheet Object. This will open a dialog where you can paste the CSS from step 1. Be sure to give the Stylesheet an appropriate name. I chose ORACLEJET_FF. Uploading a Stylesheet in this manner creates a managed Stylesheet definition that is accessible from Application Designer, and mixed with all of the other App Designer Stylesheet search results, so naming is important. Be sure to use your site-specific prefix. PeopleTools best practice recommends using the _FF suffix as well to denote this Stylesheet as a Free Form Stylesheet, which differs from the more traditional structured PeopleTools Stylesheet.

3. Use the AddStylesheet PeopleCode function in a Page Activate event

To any page that will use Oracle JET styling, open the PageActivate event and add PeopleCode that looks something like:

AddStyleSheet(StyleSheet.ORACLEJET_FF);
4. Add Oracle JET class names to fields on your page

Now open your Fluid page in Application Designer and identify the field that is supposed to use an Oracle JET class. Open the field's properties and switch to the Fluid tab. In the Fluid tab, set the Default Style Name to an Oracle JET style class. My favorite Oracle style classes to use with Fluid are oj-flex and oj-flex-item because they allow me to use CSS Flex Layout with Groupbox containers to ensure proper responsive design across a variety of form factors.

I think it is safe to say the most popular CSS framework in the world is Bootstrap. A common question developers ask me is, "Can I use Bootstrap with PeopleSoft Fluid?" The answer, of course is, "Yes." While I prefer Oracle JET because of its no-tag alternative, I have friends that successfully use Bootstrap with PeopleSoft Fluid pages. With PeopleSoft, all things are possible!

Alliance 2017

Thu, 2017-02-09 14:07

We are in the final countdown for Alliance 2017. I am really excited about this conference. It is such a great opportunity to meet up with old friends as well as make new ones. The Alliance session content and delivery is extremely high caliber. HEUG is a very engaged community. The MGM is a pretty amazing facility as well.

At GreyHeller our week starts with an amazing Monday workshop. On Monday, February 27th from 10:00 AM to 2:30 PM, Larry Grey and I will be hosting a pre-conference workshop titled Advanced PeopleTools Development Workshop with Jim Marion (session 4378). Our objective is to give you hands on experience with all of the new PeopleTools including Fluid and the Event Mapping Framework. But wait, there's more... Fluid itself is a new development paradigm with a lot of flexibility. In this session you will learn how to use CSS and JavaScript to further enhance the PeopleSoft user experience. For more details and registration, visit the Monday Workshops page on the Alliance conference site.

On Tuesday morning at we join our partner MODO LABS at 8:30 AM to present the session A Student for Life - Engaging prospective, new, current, and past students has never been easier. In this session you will see how MODO LABS partnered with GreyHeller makes it trivial to embed PeopleSoft content in a native, secure user experience giving users access to native, on-device capabilities such as maps, notifications, etc.

On Tuesday, February 28th from 09:45 AM to 10:45 AM, our friends from the University of Massachusetts will be sharing about their experience mobilizing and modernizing the Student Center (session 4036) at their UMass Boston, Dartmouth and Lowell campuses using our PeopleMobile™ product. It really is amazing how our product transforms the PeopleSoft user experience. Definitely a "must see."

On Tuesday, February 28th from 1:15 PM to 3:15 PM, Larry and I will be leading the PeopleSoft Cloud to Ground workshop – Cloud Adoption Strategies and Best Practices (session 4381). In the ERP space, Hybrid "is the new black." There are a lot of great cloud enhancements to a traditional ERP. Anyone thinking about implementing cloud is also thinking about backend data integrations. But what about the user experience? You don't have to settle for a disjointed user experience. In this session, Larry and I will show you how your organization can integrate the UX layer into a single, common user experience.

On Thursday, March 2nd at 9:15 AM, my friend Felicia Kendall from UCF will be sharing about their highly publicized breach (including costs) and their experiences with securing PeopleSoft after a highly publicized breach. This should prove to be a very valuable session. The session is titled University of Central Florida: Post-breach Mitigation & Prevention Strategy (session 4108).

While attending Alliance, be sure to wander through the demo grounds. Our booth (#301) will be right beside the Oracle booth. I'm looking forward to wandering through and visiting with my friends from Oracle, Ciber, Deloitte, Gideon Taylor, Intrasee, Smart ERP, Accenture, Presence of IT, MODO LABS, Huron, Sierra-Cedar, and many more.

See you on the floor!

Cloud to Ground Mashup Webinar

Mon, 2016-10-24 23:45

At 11:00 AM Pacific on Tuesday, October 25th (tomorrow), I have the privilege of talking about Cloud and on-premise (ground) integration. Whether cloud to cloud, cloud to ground, or ground to ground, integration is probably one of the most difficult aspects of any implementation. Integration comes in two flavors:

  • Back-end
  • Front-end

Back-end integration is the most common. Back-end integration involves integrating data between two systems either for processing or presenting a common user experience.

Front-end integration is about combining the user experience of two separate applications to create a common user experience. I often find that I can eliminate some of the back-end integrations if I can appropriately mashup front-end applications. In this webinar you will learn enterprise mashup strategies that allow you to present a seamless user experience to your users across cloud and ground applications. No modifications. Just tailoring and configuration.

VirtualBox Manual DPK Import Failure: Ran out of Virtual Disk

Tue, 2016-10-04 00:33

The DPK scripts are simply amazing. I enjoy the flexibility of the new DPK system. I will confess, creating an HCM demo environment with DPK is not as easy as the prior PUM image method, but it is pretty close. As I prepared for OpenWorld 2016, I thought I would download the latest HCM DPK (update 18) and build out a new demo server on my MacBook. Unfortunately, I wasn't able to use the standard Windows PowerShell approach (PowerShell on Mac? Yes, maybe...) so went with the manual VirtualBox import method described in the document: PeopleSoft_Deployment_Packages_For_Update_Images_Installation_July2016, page 31 Task 2-2-2. Everything was running great until the VM attempted to extract HCM-920-UPD-018-OVA_12of15. The install process seemed to hang. With a little investigation, I found that the VM's second disk was full. The solution was rather simple: expand the disk and try again. Just in case you find yourself in this situation, here are the steps I performed to expand the virtual disk.

  1. Following the manual steps, I first imported the VirtualBox shell appliance, but I didn't boot the image
  2. Next, I cloned the second disk using the command VBoxManage clonehd VBOX_8_55_06_SHELL-disk2.vmdk VBOX_8_55_06_SHELL-disk2.vdi --format vdi. The point of cloning into a VDI is so we can use VirtualBox commands to expand the disk.
  3. I then expanded that new disk using the command VBoxManage modifyhd VBOX_8_55_06_SHELL-disk2.vdi --resize 122880. I didn't need to make the disk 120 GB. VirtualBox tells me the image is only using 65 GB, but it doesn't hurt to have extra capacity. The disk files grow as needed.
  4. Optional step: If you want, you can convert the disk back to a VMDK, but this is not necessary. I kept the VirtualBox VDI. VBoxManage clonehd VBOX_8_55_06_SHELL-disk2.vdi VBOX_8_55_06_SHELL-disk2.vmdk --format vmdk.
  5. You need to tell VirtualBox to use the new disk you just created. Open the Virtual Machine's settings and switch to the storage section. Replace the exising *disk2 entry with the name of the file you just created.
  6. Now, here is the interesting part... The virtual disk is bigger, but the operating system doesn't know that yet. We have to stretch the partition table on that disk so the operating system can use the free space we just created. The way I handled this was to boot the VirtualBox guest using one of the amazing Linux live ISO distributions. Specifically, I chose GParted. So, your next step is to download a Linux live distribution. You can find the GParted ISO here. Download the ISO so you can make it available to the VirtualBox guest
  7. With the ISO downloaded, open the guest's properties and switch to the storage settings. Add an optical drive to the IDE controller and select the ISO you downloaded.

  8. Boot the Virtual Machine. The live CD image should take over. If you chose GParted, then you should see the GParted program load. Use the list of disks in the upper right corner to switch to sdb. You should now see a disk with lots of unallocated space. Edit this disk so that it uses all of the available space

  9. Apply your changes, shutdown the virtual machine, and then remove the GParted disk ISO from the virtual drive.
  10. Continue with the rest of the DPK Install steps as described in the Oracle provided documentation.

You should now have a fully functional VirtualBox demo image. Tip: if your usage is light (no SES, not running payroll, etc), then you can easily drop the allocated memory for your VirtualBox image down to 2 GB. I've even run them as low as 1 GB. Memory is important, but I derive the most performance improvement from running these images on an SSD.

October 2016 Webinars

Mon, 2016-10-03 21:38

I will be delivering three webinars this month, with two of them this week:

PeopleSoft Tips and Techniques: Advanced PeopleSoft People Tools Development Strategies

Wednesday, October 5th at 11 AM PST / 2 PM EST

In this session I will share interesting, thought provoking PeopleTools tips and techniques to help customers make the most of their PeopleSoft development investment.

Mobility Options for PeopleSoft Applications

Thursday, October 6th at 11 AM PST / 2 PM EST

Attend to learn various options for mobilizing PeopleSoft Applications.

The Cloud to Ground Mashup

Tuesday, October 25th at 11 AM PST / 2 PM EST

Presenters: Jim Marion, Senior Technology Evangelist
Larry Grey, Co-Founder

Why choose between Cloud and Ground when you can get the best of both? In this demo-intensive session, we will illustrate the flexibility and safety of your PeopleSoft investment.

You can register for our Webinars on the GreyHeller website.

OpenWorld 2016

Fri, 2016-09-09 18:01

In a matter of days, customers, partners, employees... and everyone else related to the Oracle ecosystem will converge on San Francisco for the 2016 edition of the Oracle OpenWorld conference. This is by far the most comprehensive Oracle conference of the year, covering both applications and technology. I am really looking forward to attending this year as an Oracle partner. Since I won't be working the Oracle demo grounds, please feel free to stop me anytime, anywhere to start an impromptu conversation. Don't hesitate. My goal is to talk to as many people as possible, so you will be doing me a favor by saying "Hello."

Like many of you, I have been searching the content catalog for all of my favorite search terms: Fluid, PeopleTools, Jeff Robbins, Graham Smith, Sasank Vemana, David Bain, JavaScript, and Oracle JET... there is a lot of great content this year. Unfortunately, there are also a lot of really great overlapping sessions. In fact, the famous Graham Smith from Cedar Consulting will be presenting at the exact same time as his wife Jo, but in a different room (Sorry Graham, I am planning to attend your wife's session).

For PeopleTools-minded individuals, the conference starts early on Sunday with Sasank's Life Hacks for PeopleSoft Development [UGF2499]. Sasank's sessions are always worth attending.

With all that is going on at OpenWorld this year, I do hope you will leave room in your schedule to attend my session on Monday at 4:15PM in Moscone West 2024. I will be presenting Getting the Most Out of PeopleSoft: PeopleSoft PeopleTools Tips and Techniques [CON7070]. I have a lot of great content planned including new, under-publicized PeopleTools 8.55 features, fluid tips, and Oracle JET. As always, I expect to give away a few copies of my books as well (remind me if I forget).

The OpenWorld bookstore is another one of my favorite places to visit during the OpenWorld conference. Oracle Press will be there with copies of all three of my books. Each year the bookstore sells these books at a discount. If you purchase a copy, look for me in Moscone West. I would be happy to sign your book for you.

See you soon!

Dynamic Java in PeopleCode

Fri, 2016-07-22 04:06

The PeopleCode language has a lot of features and functions. But sometimes, it seems there are tasks we need to accomplish that are just out of reach of the PeopleCode language. It is at these times that I reach for Java. I have written a lot about Java, so I'm sure many of you already know how to mix Java with PeopleCode. While certainly a rational solution, one of the pain points of a Java solution is managing custom Java on the app and process scheduler servers. Each time you update your compiled class (or jar) files, you have to restart the server. That might be OK in a stable production environment, where you don't intend to change code often, but in development, it is a real pain! Likewise, maintaining custom Java class and jar files through upgrades can be a little sketchy. Specifically, if you redeploy PeopleTools or rewrite psconfig, then it is possible you may miss some of your custom Java code. PeopleBooks tells us how to setup psconfig for custom Java classes, but again, that is just one more thing to manage through upgrades. Now, imagine being able to update your custom Java code with a Data Mover script. Further, imagine being able to run custom Java without making any changes to your application server. Imagine what it would be like to run Java without having to beg (or bribe) your admin for a "no customization" exception. It is possible today. The answer: Use JavaScript to interface between PeopleCode and the delivered Java Runtime Environment. Through the embedded Mozilla Rhino JavaScript script engine of Java, we have full, dynamic access to the JRE. When and how would you use this? Let's review some examples.

Custom HTTP Connections

For various reasons, some customers choose not to implement Integration Broker. These customers find themselves requiring integration, but without IB's networking features. An alternative to %IntBroker.ConnectorRequestURL is to use Java's HttpURLConnection.I strongly discourage this approach, but the question arises. The JRE is there, well integrated with PeopleCode, and ready for use. From PeopleCode, it is possible to create a Java URLConnection using CreateJavaObject("java.net.URL", "http...").openConnection(). A problem arises when we try to invoke methods of a HttpURLConnection, the real return value of URL.openConnection. Unfortunately, PeopleCode doesn't see it that way, which leads down the reflection path (we don't want to go there). This is where JavaScript can help us. JavaScript doesn't mind that URL.openConnection returned an HttpURLConnection even though it said it would just return a URLConnection. Here is an example:


var result = (function() {
// declare pointers to Java methods to make it easier to invoke the methods
// by name later
var URL = Packages.java.net.URL;
var InputStreamReader = Packages.java.io.InputStreamReader;
var BufferedReader = Packages.java.io.BufferedReader;
var StringBuilder = Packages.java.lang.StringBuilder;

var serverAddress = new URL(
"http://hcm.oraclecloud.com/hcmCoreApi/atomservlet/employee/newhire"
);


// Creates an HttpURLConnection, but returns URLConnection. If I was using
// PeopleCode, PeopleCode would see this as a URLConnection. To invoke
// HttpURLConnection methods, I would need to resort to reflection. This is
// the power of JavaScript in this scenario...
var connection = serverAddress.openConnection();

// ... for example, setRequestMethod is NOT a method of URLConnection. It is
// a method of HttpURLConnection. PeopleCode would throw an error, but
// JavaScript recognizes this is an HttpURLConnection and allows the method
// invocation
connection.setRequestMethod("GET");

// Timeout in milliseconds
connection.setReadTimeout(10*1000);

// Finally, make the connection
connection.connect();

// Read the response
var reader = new BufferedReader(
new InputStreamReader(connection.getInputStream()));
var sb = new StringBuilder();
var line;

while ((line = reader.readLine()) !== null) {
sb.append(line + '\n');
}

// Return the response to PeopleCode. In this case, the response is an XML
// string
return sb;
}());

Excel Spreadsheets

PeopleTools 8.55+ has a PeopleCode API for Excel, which means this solution is now irrelevant. I'm listing it because not everyone is up to PeopleTools 8.55 (yet). If you use this idea to build a solution for 8.54 and later upgrade, Oracle recommends that you switch to the PeopleCode Excel API. The solution will still work with 8.55+, but just isn't recommended post 8.54.

This solution uses the Apache POI library that is distributed with PeopleTools 8.54+ to read and write binary Microsoft Excel files. As with the networking solution above, it is possible to use POI directly from PeopleCode, but a little difficult because POI uses method overloading in a manner that PeopleCode can't resolve. Furthermore, POI uses methods that return superclasses and interfaces that PeopleCode can't cast to subclasses, leading to awful reflection code. Here is an example that reads a spreadsheet row by row, inserting each row into a staging table for later processing.


// endsWith polyfill
if (!String.prototype.endsWith) {
String.prototype.endsWith = function(searchString, position) {
var subjectString = this.toString();
if (typeof position !== 'number' || !isFinite(position) ||
Math.floor(position) !== position ||
position > subjectString.length) {
position = subjectString.length;
}
position -= searchString.length;
var lastIndex = subjectString.indexOf(searchString, position);
return lastIndex !== -1 && lastIndex === position;
};
}

// open a workbook, iterate over rows/cells, and then insert them into a
// staging table
var result = (function() {
// declare pointers to Java methods to make it easier to invoke the methods
// by name
var FileInputStream = Packages.java.io.FileInputStream;

var HSSFWorkbook = Packages.org.apache.poi.hssf.usermodel.HSSFWorkbook;
var Workbook = Packages.org.apache.poi.ss.usermodel.Workbook;
var XSSFWorkbook = Packages.org.apache.poi.xssf.usermodel.XSSFWorkbook;

// declare a PeopleCode function
var SQLExec = Packages.PeopleSoft.PeopleCode.Func.SQLExec;

// internal "helper" function that will identify rows inserted into
var guid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g,
function(c) {
var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
return v.toString(16);
}
);

// open a binary Microsoft Excel file
var fis = new FileInputStream(fileName);

var workbook;

if(fileName.toLowerCase().endsWith("xlsx")) {
workbook = new XSSFWorkbook(fis);
} else if(fileName.toLowerCase().endsWith("xls")) {
workbook = new HSSFWorkbook(fis);
}

var sheet = workbook.getSheetAt(0);
var rowIterator = sheet.iterator();
var roleName,
descr,
row;

// iterate over each row, inserting those rows into a staging table
while (rowIterator.hasNext()) {
row = rowIterator.next();
roleName = row.getCell(0).getStringCellValue();
descr = row.getCell(1).getStringCellValue();

// TODO: turn this into a stored SQL definition, not hard coded SQL
SQLExec("INSERT INTO PS_JM_XLROLE_STAGE VALUES(:1, :2, :3, SYSTIMESTAMP)",
// notice that the SQLExec parameters are wrapped in an array
[guid, roleName, descr]
);
}

// return the unique identifier that can later be used to select the rows
// inserted by this process
return guid;

}());

Here is an example of writing/creating a Microsoft Excel spreadsheet:


var result = (function() {
// import statements
var XSSFWorkbook = Packages.org.apache.poi.xssf.usermodel.XSSFWorkbook;
var FileOutputStream = Packages.java.io.FileOutputStream;

// variable declarations
var workbook = new XSSFWorkbook();
var sheet = workbook.createSheet("Countries");
var fileName = "c:/temp/countries.xlsx";

var row = sheet.createRow(0);
var cell = row.createCell(0);

cell.setCellValue("United States of America");
cell = row.createCell(1);
cell.setCellValue("USA");

row = sheet.createRow(1);
cell = row.createCell(0);
cell.setCellValue("India");
cell = row.createCell(1);
cell.setCellValue("IND");

row = sheet.createRow(1);
cell = row.createCell(0);
cell.setCellValue("Denmark");
cell = row.createCell(1);
cell.setCellValue("DNK");

var fos = new FileOutputStream(fileName);
workbook.write(fos);
fos.close();

return "Created workbook " + fileName;

}());

JSON Parsing

If your goal is to convert a JSON string into SQL insert statements, then this is a very painless alternative:


/* Sample JSON data that will be selected from a record definition
[
{"emplid": "KU0001", "oprid": "HCRUSA_KU0001"},
{"emplid": "KU0002", "oprid": "HCRUSA_KU0002"},
{"emplid": "KU0003", "oprid": "HCRUSA_KU0003"}
];*/

var result = (function() {
var CreateRecord = Packages.PeopleSoft.PeopleCode.Func.CreateRecord;
var Name = Packages.PeopleSoft.PeopleCode.Name;
var SQLExec = Packages.PeopleSoft.PeopleCode.Func.SQLExec;

// example of how to reference a PeopleCode record definition from
// JavaScript. Later we will select JSON_DATA from this table
var rec = CreateRecord(new Name('RECORD', 'NAA_SCRIPT_TBL'));

var count = 0;
var json_string;
var json;

var guid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
return v.toString(16);
});

// Select JSON string from a table. Normally this would come from a variable,
// a service, etc. Here it makes a great example of how to select rows from
// a record definition
rec.GetField(new Name('FIELD', 'PM_SCRIPT_NAME')).setValue('JSON_TEST_DATA');
rec.SelectByKey();
json_string = rec.GetField(new Name('FIELD', 'HTMLAREA')).getValue();

// now convert that received string into an object.
json = JSON.parse(json_string);

// Iterate over json data and...
json.forEach(function(item, idx) {
// ... insert into a staging table
SQLExec("INSERT INTO PS_NAA_TEST_TBL VALUES(:1, :2, :3, SYSTIMESTAMP)",
// notice the array wrapper around SQLExec bind values
[guid, item.emplid, item.oprid]
);
count += 1;
});

return "Inserted " + count + " rows";

}());

I could go on and on with examples of creating zip files, encrypting information, base64 encoding binary data, manipulating graphics using Java 2D, etc, but I think you get the idea.

HIUG Interact 2016 Agenda

Mon, 2016-05-23 16:30

In a couple of weeks, I will be presenting the following sessions at the HIUG Interact 2016 conference in San Antonio

  • 16165 : PeopleSoft Fluid User Interface – Deep Dive: Grand Oaks D, Mon, Jun 13, 2016 (03:15 PM - 04:15 PM)
  • 16164 : PeopleTools Tips & Techniques: Grand Oaks D, Tue, Jun 14, 2016 (02:30 PM - 03:30 PM)
  • 16163 : Tech Clinic: Application Designer Grand Oaks D, Wed, Jun 15, 2016 (12:30 PM - 02:30 PM)

Collaborate 2016

Tue, 2016-03-29 17:28

I just wanted to post a quick note with my schedule for Collaborate 2016. I will be presenting PeopleSoft Developer Tips and Techniques on April 13th at 9:15 AM in Reef C. My publisher assured me that I will have a box of books waiting for me at the conference and we plan to give them away at various sessions during the conference. Here are a couple of other sessions I recommend attending at Collaborate:

There is one session I want to specifically highlight: A Designers Intro to the Oracle JET Framework. PeopleTools 8.55 includes Oracle JET, Oracle's brand new open source JavaScript Extension. You may never directly interact with Oracle JET, but it is always good to understand the tools and frameworks used by PeopleTools. Oracle JET is based on common JavaScript libraries such as RequireJS and Knockout and PeopleTools includes these related open source libraries (note: I have written about using RequireJS with PeopleSoft in prior posts).

Alliance 2016

Thu, 2016-03-03 02:54

HEUG Alliance is next week. I hope you are registered. I know this will be a fun and informative conference (as always). I am scheduled for two sessions at Alliance:

  • PeopleSoft Developer: Tips and Techniques on Monday at 3:30 PM in room 6B/C and
  • PeopleSoft Meet the Experts on Tuesday at 10:15 AM in room 307/308 table 2.

When I'm not in sessions, you will find me in the demo grounds. Stop by and say "Hello!"

JavaScript and PeopleCode Array Parameters

Tue, 2015-11-10 18:29

I have been experimenting with scripting PeopleCode using JavaScript. This is possible because Java includes Mozilla's Rhino JavaScript engine. I took one of my experiments to OpenWorld 2015 which shows creating a Microsoft Excel Spreadsheet using POI and JavaScript. Here we are, a couple of weeks later, and I see this in the PeopleSoft OTN Discussion Forum: Java Exception: java.lang.reflect.InvocationTargetException: during call of java.lang.reflect.Method .invoke. Perfect! That is my exact use case from OpenWorld. I just happen to have a code sample to share on the forum. The developer's scenario was a bit more complicated. As you will note from the forum post, the developer needed to invoke SQL.Fetch from JavaScript. The JavaScript version of SQL.Fetch, which uses the PeopleCode Java interface, requires an array of selected columns. My first thought was just to use a standard JavaScript array. Since the SQL only has one column, I just needed an array with one item. This didn't work. JavaScript Arrays clearly are not Java Arrays. Here is an example:

var result = (function() {  
var ReflectiveArray = java.lang.reflect.Array;
var CreateSQL = Packages.PeopleSoft.PeopleCode.Func.CreateSQL;
var columns = ReflectiveArray.newInstance(java.lang.Object,
1 /* number of selected columns */);
var results = [];

SQL = CreateSQL("SELECT OPRDEFNDESC FROM PSOPRDEFN WHERE ROWNUM < 10");
while (SQL.Fetch(columns)) {
results.push(columns[0]);
}

return results.join();

}());

Western Canada Regional Users Group 2015

Mon, 2015-11-09 17:29

I will be in Calgary next week presenting PeopleTools topics at the Western Canada Regional Users Group meeting. My sessions are at 1 PM and 2:15 PM. See you there!

OpenWorld 2015 Presentations Available

Fri, 2015-11-06 12:06

OpenWorld 2015 presentations are now available in the content catalog. My PeopleTools Tips and Techniques session presentation is available here.

Pages