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!

Friday, December 22, 2017

Weekend Fluid Classes

Habit 7: Sharpen the saw. We all know we must sharpen our mental saw. The challenge is fitting saw sharpening into our schedule. If you are a billable consultant dedicated to a project, then you are often bound by contract to be onsite during regular business hours. If you find yourself in this situation, sharpening your technical skills can be challenging because most training courses happen during your committed working hours. We understand the struggle and want to do what we can to help. With that in mind, we are offering our popular Fluid training series over two consultant weekends (Friday and Saturday) of January:

  • Fluid 1: Jan 12-13, 2018
  • Fluid 2: Jan 26-27, 2018

Further details and registration information are available on our website

Wednesday, December 20, 2017

Using CSS Frameworks with PeopleSoft Fluid

Fluid is Oracle's strategic direction. If you have experience with Fluid development, you know that dragging, dropping, and aligning fields on a canvas isn't enough to develop a multi-form factor Fluid user interface. With Fluid comes an emphasis on CSS3 for multi-form factor support. This means you must add CSS class names to page fields to support different screen sizes. PeopleTools comes with an extensive list of predefined CSS class names. The challenge is identifying which CSS class to use. Fortunately, Oracle published two extremely helpful documents:

If you learn the style classes in these two documents, you will do quite well with Fluid layout (actually, just learn a few dozen of these classes and you will do quite well). But what if you want to use features of CSS that don't exist in Fluid-delivered CSS classes? Flexbox, for example, is one of the most powerful features of CSS3. When I find myself terribly annoyed with Fluid layout, I throw a Flexbox at my layout issue and the problem is solved. If we want to apply styling using CSS attributes that PeopleSoft hasn't already defined, we have two options:

  • Define our own CSS classes by creating a free-formed stylesheet or
  • Borrow someone else's CSS class names from a CSS framework.

Writing your own CSS can be a rewarding experience. I can often solve layout problems with just a few lines of CSS. My concern, however, is maintenance. What starts as a one-time CSS "fix" (or hack) for a layout often turns into a copy/paste exercise replicated at least a dozen times, with each page using some of the same CSS and some different CSS. Then what? Do we create a separate CSS file for each Fluid page? Do we refactor common CSS, moving similar code into a shared library?

Given the age and history of the internet, most web layout problems have already been solved. Since PeopleSoft is just another web application, we can leverage the work of the world wide web's pioneers. The solutions to most of our layout problems exist in today's common CSS frameworks, Boostrap being the most popular. There are many PeopleSoft consultants happily using Bootstrap to enhance PeopleSoft Fluid pages. Here is how they do it:

  1. Import Bootstrap into a Freeform Stylesheet
  2. Use AddStylesheet to insert Bootstrap into a Peoplesoft page
  3. Apply Bootstrap style classes to Fluid page elements
  4. Create a "reset stylesheet" to fix everything Bootstrap broke.

Yes, you read that last line correctly, "... fix everything Bootstrap broke." Please don't misread this. There are many developers successfully using Bootstrap with PeopleSoft. But here is the problem: Most CSS frameworks directly style HTML elements. This is actually good. Developers call this a "reset" stylesheet. What makes this a problem for PeopleSoft is that PeopleTools ALSO applies CSS directly to HTML elements. PeopleTools includes its own reset stylesheet. In a sense, we could say that Fluid is a CSS framework itself. The end result is a mixture of styles applied to HTML elements by two competing and complementing CSS frameworks. I call this "Fluid-strap." Consultants work around this problem by creating a reset for the competing reset stylesheets — a reset for the reset.

Here is another alternative: Use a CSS framework that does NOT style HTML elements, but instead relies on class names. This type of CSS framework was designed for compatibility. This type of framework understands that another CSS framework is in charge. My personal favorite CSS compatibility library is Oracle JET. In Oracle JET's GitHub repository, you will find oj-alta-notag.css, a CSS file containing a lot of CSS class names and no element declarations. To use this library, follow the first three steps described above, skipping the final step:

  1. Import oj-alta-notag.css into a Freeform Stylesheet
  2. Use AddStylesheet to insert the Oracle JET Stylesheet into a Peoplesoft page
  3. Apply Oracle JET style classes to Fluid page elements

The key difference is we don't have to create a reset for the reset. The Oracle JET stylesheet silently loads into a PeopleSoft page without changing any styling unless specifically asked to style an element through the element's Default Style Name (Style Classes on 8.56) property.

Consider a PeopleSoft page built with 4 group boxes aligned horizontally as demonstrated in the following screenshot.

In Classic, what you see is mostly what you get, so the online rendering would look nearly the same as the Application Designer screenshot. In Fluid when viewed online, however, each group box will render vertically as follows:

We can fix this by applying a CSS Flexbox to the 4 group boxes. With Flexbox, the 4 group boxes will align horizontally as long as the device has enough horizontal real estate. If the display is too small, any group boxes that don't fit horizontally will move to the next row. For this example, we will use Oracle Jet's Flex Layout.Here are the steps

  1. Add a Layout only Group Box around the 4 horizontal group boxes and mark the container group box as Layout Only
  2. While still setting container group box properties, set the group box's style class to oj-flex oj-sm-flex-items-1
  3. Likewise, to each of the 4 horizontal group boxes, add the Style Class oj-flex-item
  4. Create a Freeform stylesheet definition containing oj-alta-notag-min.css
  5. Use the AddStylesheet PeopleCode function in PageActivate to insert the Stylesheet into your page

The end result will look something like this:

Several years ago, I read the book Test Driven Development by Kent Beck. In that book, Kent identifies the first step of each project as the hardest step. Why? Because each new project contains significant uncertainty. Software development seems to involve a lot of unknowns (if the solution was known, someone would have created it, automated it, and published it). His advice? Start with what you know. You start with what you know and work torwards what you don't know. This is how we teach Fluid at JSMPros. Your developers understand Classic development and we use that knowledge to springboard students into a higher level of Fluid understanding. If you are ready to take the Fluid challenge, I encourage you to register for one of our monthly Fluid classes at www.jsmpros.com/training-live-virtual. Have a group of eight or more developers? Contact us to schedule your own personalized Fluid training event.

Monday, December 18, 2017

Is Your Fluid Custom Action Menu Page-based or Component-based?

In the upper right corner of the Fluid header you will find the "Hamburger" (or hamburger-light) menu. This 3-bar (or 3-dot) icon displays a list of component-specific actions. For the most part these actions are rather generic, allowing a user to add a tile to a homepage or view preferences. Occassionaly we see a component that has its own custom actions. The HCM My Team and Company Directory components are great examples of components with custom action menu items. In fact, the Fluid homepage itself is an example, with its Personalize Homepage custom action.

As developers, we can add custom component-specific actions to the header menu of a component by adding a special group box (Custom Action Menu) to one of the component's pages. When PeopleSoft loads a page with a Custom Action Menu group box, it moves the contents of that group box into the header action menu. Any actions loaded from a page stay resident in the menu as long as the component is in scope. So, technically, all Custom Action Menus are component-based. Here is my struggle:

Actions inserted into the header menu have component scope, not page scope, but are defined at the page level.

Custom action menu items are not visible (in fact, don't exist) until the page defining these menu items is loaded into the user's browser. If a component may have multiple pages, into which page would you insert the custom action menu? Logically, I might say, "The first page, of course." But here is the problem: there really is no "first page." There is a page that the component will load by default. We might call that the front door. PeopleSoft, however, lets users enter through the side door, back door, and all windows as well. By adding ?Page=... to a component URL, I can enter that component from any page within the component. For example, if I want to open the User Profile component to the User Queries page, I add PAGE=USER_QUERY to the end of the URL. If I enter a component through the wrong starting page, then I won't see the component's custom actions in my header menu.

You don't think your users will enter a page name in the URL? Probably not. Why worry about a scenario that will never happen? Actually, it isn't my users that concern me. It is system generated messages and processes with URLs. It is workflow notifications that attempt to simplify workflow by taking me to the next step in a business process.

If a user can enter a component through any page and component specific actions must be defined on a page, then into which page should I enter component-specific actions? While teaching a Fluid class and challenging students to find a solution to this problem, one of my students asked me, "What if you put the menu definition in a Footer page?" Wow! What a great idea! If you have component-scoped actions, why not define them in a component-scoped page? This page will load as soon as the component loads, regardless of the entry point. If you already have a footer page in your component, perfect! Just add the Custom Action Menu group box to your existing footer page. If you don't have a footer page, should you add one? I say, "Yes." Properly built, a footer page may be entirely invisible, used just for component-specific banner (also known as the header) changes (including Custom header left, right, and bottom sections). Seems ironic doesn't it? If you want to change the header, add content to the footer. Here is a sample footer page built specifically to hold a menu. By clearing the CSS class name from the outer container Group Box, the footer becomes invisible.

The following screenshot shows how the menu will appear when PeopleSoft reparents the menu's HTML from the footer into the header component action menu.

Interested in learning more about Fluid? I offer at least one virtual Fluid training class per month. You can learn more about our current offerings on our Live Virtual Training page. If your organization has more than 8 employees, you may derive cost savings by hosting your own training event. Learn more at www.jsmpros.com/training.

Follow my blog with Bloglovin

Wednesday, November 01, 2017

November/December Online Class Offerings Posted

We posted our online class offerings for November and December, 2017. You can find details on our website. Our Fluid classes have been extremely popular, so we will continue offering those at least once per month. Our Fluid 1 class is designed to get a developer up and running with Fluid as fast as possible. Through hands-on activities, you will use familiar tools and existsing skills to build Fluid components. No web development experience necessary. If you are a PeopleSoft developer, then Fluid 1 will teach you what you need to know to build Fluid pages. But there is more to Fluid than just dragging and dropping fields on a page. Fluid 2 takes your Fluid game to the next level by teaching you Fluid design patterns and Fluid techniques to build Fluid pages the way Oracle builds Fluid pages.

November, 2017

Course Title Date Start Time End Time Duration  
AWE Nov. 7, 2017 9:00 AM PDT 4:00 PM PDT 2 days Register
Fluid 1 Nov. 27, 2017 9:00 AM PDT 4:00 PM PDT 2 days Register
Fluid 2 Nov. 29, 2017 9:00 AM PDT 4:00 PM PDT 2 days Register



December, 2017

Course Title Date Start Time End Time Duration  
Fluid 1 December 11, 2017 9:00 AM PDT 4:00 PM PDT 2 days Register
Fluid 2 December 13, 2017 9:00 AM PDT 4:00 PM PDT 2 days Register
PeopleTools
8.55/8.56 Delta
December 19, 2017 9:00 AM PDT 4:00 PM PDT 2 days Register

Thursday, October 12, 2017

Identifying Conditional Navigation Content References

As PeopleSoft customers upgrade to Fluid-enabled applications, it is quite common to start with Fluid disabled, and then implement Fluid behavior post go-live. It is all about change management and an organization's ability to digest change. Even though Oracle has set retirement dates for certain Classic components, with the first wave retiring December 31, 2017, all Classic functionality is still supposed to be present and available. But if you have opened a recent PeopleSoft image or are in the middle of updating to a recent HCM build, you may be asking, "Where are those delivered and supported Classic components?" If you investigate the portal registry, you will see they exist, they just don't appear in any menus. Many of these menu items, such as the Personal Details menu items, use a feature named Conditional Navigation. PeopleSoft uses Conditional Navigation to conditionally replace Classic menu items with their Fluid counterparts. You can read more about Conditional Navigation in PeopleBooks at PeopleTools PeopleBooks entry Products > Development Tools > Portal Technology > Understanding Conditional Navigation. A question I hear regularly is, "How do I temporarily enable Classic components in my PeopleTools 8.55+ environment?" The first step is to disable Conditional Navigation and the MyOracle Support document 2215964.1 describes how. Armed with this information, you now know what it is, how to configure it, and how to disable it, but how do you find it? I mean, what components are preconfigured for conditional navigation? Since conditional navigation uses CREF attributes, CREF identification requires a small SQL statement:

That is nice, but the portal registry isn't structured that way. Next question: How do I find those CREFs in the portal registry? Here is the longer form SQL statement. Results contain complete paths to conditionally configured items. Those of you that follow my blog may recognize this SQL as a derivative of Query for Component and/or CREF Navigation Take II

Wednesday, October 04, 2017

October Fluid Training

Last month I announced two new training courses: Fluid 1 and Fluid 2. We ran these courses at the end of September. For those of you that weren't able to attend the September session, we scheduled another series for October:

Both sessions run for 2 days from 9 AM Pacific until 4 PM Pacific. 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.

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 1

Technology 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 2

Advanced 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

Thursday, September 07, 2017

New Fluid Training Classes, Live and Virtual

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 1

Technology 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 2

Advanced 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

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.

Wednesday, June 21, 2017

New Window Bookmarklet

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.