Tuesday, May 23, 2017

Using CSS Frameworks with Fluid

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:


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!

Thursday, February 09, 2017

Alliance 2017

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!

Monday, October 24, 2016

Cloud to Ground Mashup Webinar

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.

Monday, October 03, 2016

VirtualBox Manual DPK Import Failure: Ran out of Virtual Disk

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

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. Recording available here.

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.

Friday, September 09, 2016

OpenWorld 2016

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!

Friday, July 22, 2016

Dynamic Java in PeopleCode

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(
    // 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
    // Timeout in milliseconds
    // Finally, make the connection

    // 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,

    // 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
            // 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);

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

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

    var fos = new FileOutputStream(fileName);
    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');
    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
            // 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.

Monday, May 23, 2016

HIUG Interact 2016 Agenda

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)