Multi-page messages in XPages

The JSF framework on which XPages are built has built-in functions to display messages to users. You can see this in use if you add a validator to a field and disable clientside validation in the application properties: if the validation fails, a message is added to the FacesContext object that can be displayed on screen by including a Display Errors (<xp:messages>) control.

It is also possible to add your own custom messages by using the facesContext.addMessage() method. This was described by Tommy Valand on his blog.

A problem with the default JSF messages implementation is that the messages are gone if you open another page or redirect the user to another page. Consider for instance the case that you show a list of items to users on an XPage (using a data table or view panel) and allow a user to create a new item by redirecting him to another XPage containing a form. After the user has saved the form, you want to show him a confirmation message that the item was successfully saved or show him the ID of the created document. To be able to do this you need a way to store the messages between pages.

After some searching I found a way to do so by implementing a so called phase listener. This is basically a plugin you add to an application that allows you to execute your own custom code on predetermined phases in the JSF request lifecycle. The MultiPageMessagesSupport class from the article attaches itself to certain phases to temporarily store the messages in the sessionScope retrieve them when a (new) page is rendered.

To use this method you need to perform the following steps:

  • Create a new Java class in the “Code” section of your application (assuming you’re using 8.5.3). Call it MultiPageMessagesSupport, set the default package and add this code. The class file in the link uses the package “myPackage”.
  • Add the phase-listener from the newly created Java class to the faces-config.xml file of your application:
  myPackage.MultiPageMessagesSupport
  • Include an <xp:messages> control on all pages to show the messages to your users:
<xp:messages id=”messages1″ globalOnly=”true” layout=”list” styleClass=”messages”></xp:messages>
If you want to add a message you simply use the following code:
var type = javax.faces.application.FacesMessage.SEVERITY_INFO;
var msg = "this is my message";

facesContext.addMessage(null,
   new javax.faces.application.FacesMessage(type, msg, ""));
where “type” is one of the following:
  • javax.faces.application.FacesMessage.SEVERITY_INFO
  • javax.faces.application.FacesMessage.SEVERITY_WARN
  • javax.faces.application.FacesMessage.SEVERITY_ERROR
  • javax.faces.application.FacesMessage.SEVERITY_FATAL

I’ve created an online demo that uses the OneUI v2.1 theme (and associated classes for different type of messages). Thanks again to the people over at ClearIT Consulting for hosting this!.

The demo database can be downloaded here.

Exploring OneUI v2.1 in Domino 8.5.3

While exploring the new features of Domino 8.5.3 I found out that OneUI v2.1 is now installed automatically with the server. Since I’m using OneUI in a couple of applications I wanted to see what the differences are compared to version 2.0. My starting point was IBM’s Lotus OneUI documentation site, but the last documentation change was performed in 2009 (UPDATE 01/12/2011: the OneUI v2.1 documentation is now available). Although the structure of OneUI hasn’t changed, the examples on that site are all based on OneUI v2.0. A Google search also didn’t return much useful information so I decided to create a test page myself. This is what OneUI v2.1 looks like (change the color by clicking on one of the links in the top navigation bar). You might notice that I didn’t use the (new in 8.5.3) option to combine CSS/ JavaScript resources so you can check what stylesheets are loaded.

The biggest notable differences with the previous version are the black top bar (which more vendors seem to implement nowadays), the left column menu and redesigned sections. OneUI v2.1 also features a couple of new colors (see the demo page for all available options). Since OneUI v2.1 uses gradients in the top bars (using the -moz-linear-gradient CSS directive) the theme looks better in Firefox than in Internet Explorer 9.

To use OneUI v2.1 in your application you need to create a theme that extends oneuiv2.1. Doing so will take care of including the required basic stylesheets. A specific color can be loaded by adding two additional stylesheet per color: <color>Theme.css and dojoTheme.css, both from the domino/oneuiv2.1/<color>Theme/ folder. So if you want to use the orange stylesheet, you need to add this to your theme definition:

<resource>
<content-type>text/css</content-type>
<href>/.ibmxspres/domino/oneuiv2.1/orangeTheme/orangeTheme.css</href>
</resource>
<resource>
<content-type>text/css</content-type>
<href>/.ibmxspres/domino/oneuiv2.1/orangeTheme/dojoTheme.css</href>
</resource>
If you want to be able to make the color used configurable, you can do so be including these stylesheets conditionally. Using a sessionScope variable that would mean changing the <resource> tag to
<resource rendered="#{javascript:sessionScope.get('theme')=='orange'}">

Note that the demo also includes a Dojo tree using the (new in Dojo 1.5) Claro theme.

Thanks to the people over at ClearIT Consulting for hosting my demo page!

For all those interested: here’s the demo database.

XPages: server vs. client-side redirects

If you want to redirect a user to another XPage you can use the redirectToPage function:

context.redirectToPage( "someXPage.xsp" );
context.redirectToPage( "someXPage" );

You can also add parameters to this call if you need to:

context.redirectToPage( "someXPage.xsp?action=openDocument" );
context.redirectToPage( "someXPage?action=openDocument" );

If you want to redirect the user to another site, you can use the redirect method:

facesContext.getExternalContext().redirect("http://www.cnn.com")

All of the methods above send a “redirect” page to the browser with a 302 status code (“moved temporarily”) that includes the new location in the header. Next, the browser will perform a HTTP GET to the new location. The location in the address bar of the browser is updated and the browser displays the new page.

A disadvantage of using a client-side redirect is that it causes an extra HTTP request to be performed by the browser. There is also another method called a “forward”. Using a forward, the request forwarding is done internally by the server. The results of the internal forward (the contents of the new page) are send back directly to the browser. The browser doesn’t know of the internal forward and won’t update the URL in the address bar.

The perform a server side forward you need to add a second parameter to the redirectTo method:

context.redirectToPage( "someXPage.xsp", false);

Forget about the time when comparing dates in SSJS

I needed to compare two dates in SSJS and determine if a date was on or before tomorrow. Sounds pretty easy but it took me a while to figure this out. The hard part was that I needed to remove the time part, I only wanted to compare the dates.

As always (or most of the times) the solution turned out to be pretty simple and involved using the @Date() function. So if you ever need to do the same, just use the following (note that all dates are java.util.Date instances, not NotesDateTime objects).

//set an input date
//for this example the date is set to June 1st, 2011
//remember: in Java(script), a month is represented by an integer from 0 to 11;
//0 is January, 1 is February, and so forth; thus 11 is December
var inputDate = new Date();
inputDate.setFullYear(2011, 5, 1);

//set target date
//you can also use @Date( @Adjust(@Now(), 0,0,1,0,0,0) )
var targetDate = @Date( @Tomorrow() );

//check if the specified date is before or on the target date
var result = (@Date(inputDate).before( targetDate ) || 
    @Date(inputDate).equals(targetDate));

Error while using sessionAsSigner calls

Yesterday, apparently for no reason, I started getting all errors in my XPage application. Investigating it more I found out that the root cause was a problem with the sessionAsSigner function. The error I was getting was:

‘sessionAsSigner’ not found

So I started thinking what changed in the last week and might have caused this error. I made no configuration changes and performed no updates. The only thing I changed was that I started using the server with another user ID. I looked into the security and authentication settings and found absolutely no differences between the user ID’s.

After some more investigating I stumbled across a tweet from a user called HinkyMinky:

I resigned the database with my new user ID and all problems were solved!

Putting your Xpage to sleep

I’m currently working on rewriting my multiple-file uploader using Xpages/ custom controls. I encountered a problem when embedding the control on an Xpage based form. When the “save” button on the form is called, the uploads are started but I needed the Xpage to wait until all files had been uploaded.

My first try was to implement a sleep function in Javascript, but the only way this seemed to work was using a while loop. The big problem with that is that it eats up all CPU cycles and “locks” your browser. Not a good solution.

One of the articles I read mentioned the Java sleep method. This gave me the idea of implementing the wait-until function on the Xpage itself.

In the querySaveDocument event of the datasource on the Xpage I make a (conditional) call to the Java sleep method:

while (!condition) {

 java.lang.Thread.currentThread().sleep(1000);
 //re-check condition here

}

This causes the Xpage to wait for (in this case) 1 second (1000 milliseconds) until it continues to save the document.

To make sure that the script doesn’t go in an infinite loop I have included a timeout period:

var startTime = new Date().getTime();
var elapsedTime = 0;
var timeOutSeconds = 60;

while (!condition &amp;&amp; elapsedTime &lt; timeOutSeconds) {

 java.lang.Thread.currentThread().sleep(1000);
 //re-check condition here

 elapsedTime = (new Date().getTime() - startTime) / 1024;

}

Human Readable Dates in XPages

If I display a date/time in a view I can enable the option “show ‘today’ if appropriate”. This will replace todays (or yesterdays) date with the text “Today” (or “Yesterday”). I wanted to have the same functionality in an Xpage-based application I’m creating, but couldn’t find the XPage equivalent.

After some searching on the web I found an article written by Matt White (Human Readable dates in XPages) in which he kind-of did the same: the SSJS library he created allows you to convert dates to a “time since” string (e.g. “2 hours ago”, “6 days ago”). Since what I wanted is also another way of displaying dates I decided to extend his class (and rename it while I’m at it). You’ll find the resulting class this (compressed) text file: xpDates.zip.

Usage instructions

Download the attachment and paste the code in an SSJS script library. Add the library as a resource to the Xpage(s) you’ll want to use the new date format on.

There are 2 ways to convert dates (example for a document with a “modifiedDate” date/time field)

1. use SSJS to compute and convert a value:

var myDateValue = document1.getItemValueDate("modifiedDate");

var d = new ReadableDate();
d.toReadable(myDateValue);

or:

var d = new ReadableDate();
d.timeSince(myDateValue);

The first method will give you dates formatted like “Today 9:10”, “Yesterday 3:15”, “24 July 2:10”, the second will display the time since the specified date.

2. Bind a control to the date/time value/ field, set the display type to “String” and add a custom converter:

  • type= xp:customConverter
  • getAsObject: just enter “value” (javascript, without quotes)
  • getAsString: enter “ReadableDateConverter.getAsString()” (javascript, without quotes)

The ReadableDate class currently has 3 configuration options:

  • includeTime : will include the time when displaying a date.
  • includeSeconds : includes seconds when displaying the time.
  • includeYearIfCurrent : if true the year is always added, if false the year is only added if not the current year.

Since my app has a Dutch audience, I’ve added Dutch language strings to the class.

Beware of what getColumnValue is returning

When working with view columns that might contain multiple values, remember that the return value of the getColumnValue(“column”) method from the NotesXspViewEntry object is different depending on the number of values in the column of a specific view entry:

  • If the view entry contains a single value, a string is returned.
  • If the view entry contains multiple values, it returns a Vector.
If you always want to have a Vector as the return type, you’ll have to convert it:
var values = rowData.getColumnValue("MultiValueColumn");
if ( (typeof values).toLowerCase() == "string" ) {
 var vTmp = new java.util.Vector();
 vTmp.add(values);
 values = vTmp;
}
Converting the return type is also a possible workaround when working with nested repeat controls: if you set the Iteration to yourRowData.getColumnValue(“MultiValueColumn”) the repeat control won’t work with rows containing a single value.

Using OpenLog for logging/ debugging XPages

It probably won’t make your admins happy if you constantly use print(“this”) in you Server-Side JavaScript code to print debug or error messages to the server’s console. Luckily there is a good alternative: use OpenNTF’s OpenLog project to log everything to a central database. The only problem is that the database doesn’t contain a library for Xpages (yet).

After some digging I found out that the TaskJam app from Elguji Software does have a SSJS library to be able to log messages from to the OpenLog database. To use it:

  • Download the OpenLog database and copy it to your server.
  • Download TaskJam.
  • Copy the OpenLogXPages script library from the TaskJam template to your XPage application.
  • Open your copy of the OpenLogXPages script library and update the variable “logDbPath” to reflect the path and location of your OpenLog database.
  • The OpenLogXPages script library contains code specifically for TaskJam that tries to retrieve the location of the log database from a view called vwControlPanel: disable that code.
  • On two places in the script library a variable called location is set to session.getURL(); if not specified. This is used in the log entries to show were the message came from. Since that property doesn’t always return the correct location, I”ve changed it to view.getPageName();
  • Add the OpenLogXpages script library to every page you want to debug/ log information. You can do this by adding a “resource” to basic/ resources property of your XPage or Custom Control (clientSide = false, location = /OpenLogXPages.jss). I’ve added the library to a Custom Control that is loaded on every XPage.

You’re now able to log events or error messages in your SSJS code by using:

log.logEvent( "Hello world" );
log.logError( e.toString(), null, e );

Since you’ve already downloaded TaskJam: don’t forget to have a look at it. It contains some good examples of how to use XPages.