Archive | SSJS RSS for this section

XPages Tip: Passing an Array to a Custom Control Property

Custom properties are extremely useful for modular design and reuse by sending custom values into each instance. There are a number of data types that can be selected, but no obvious way to pass an array value. In this post, I’ll describe the issue and how to work around it.

Custom Property Data Types

The default data type for a custom property is string.

There are a number of primitive data types available in the dropdown:

Custom Property - Primitive Types

If you click the folder icons, there are a number of additional options, including Converters, Validators, and other types, but there are no data types for arrays (or vectors or JavaScript objects).

Custom Property - Property Types

String Data Type

In this case, I want a property that can accept an array of values.

To test this, I created a simple custom control with two properties, stringProperty and objectProperty, to test how they handle arrays. They both default to string as the data type.

Custom Property - 1 Custom Property

<xc:ccTest>
  <xc:this.stringProperty><![CDATA[#{javascript:return ['1', '2', '3', '4', '5'];}]]></xc:this.stringProperty>
  <xc:this.objectProperty><![CDATA[#{javascript:return ['1', '2', '3', '4', '5'];}]]></xc:this.objectProperty>
</xc:ccTest>

You can leave the data type as string, but compute an array to return. It won’t throw an error, but it will treat it like a single string.

No Data Type

You can remove the data type from the property definition and it won’t throw an error on the custom control. However, that is not a valid property, so its treated as though that property doesn’t exist.

If you’ve already set up an instance of the custom control and passed a value to it, it will throw an error on the custom control instance: Unknown property this.. It is not defined on tag .

Custom Property - Error - No Property Type Defined

Custom Data Type

Fortunately, there’s a really simple solution — you can manually type in a property type.

If you type in object as the type, it does the trick. (It effectively works as desired, although it actually accepts the data as java.util.Vector.)

Custom Property - Object Type

XPages Tip: Beware Server-Side code in Multiple onClientLoad Events

TL;DR – Server-side code in onClientLoad causes a page refresh and prevents additional onClientLoad events with server-side code from running. In this post, I’ll show an example and describe how it behaves.

Page Ready Events

It is extremely useful to have an event trigger that executes when the page is fully loaded; many JavaScript plugins wait for the page to finish loading and then run to activate widgets on the page. Because of its importance, jQuery has $(document).ready() and Dojo has dojo/domReady and dojo.addOnLoad() to make this checking easy.

The Problem with onClientLoad in XPages

XPages provides the onClientLoad event on pages, custom controls, and panels. XPages allows you to run client-side and/or server-side code in the event. However, running server-side code can cause a tough-to-troubleshoot side effect.

Take, for example, this XPage, which has two panels. Both panels and the page itself have client-side and server-side onClientLoad code to write a message to the browser console and server console, respectively.

<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">

  onClientLoad Event Testing

  <xp:eventHandler event="onClientLoad" submit="true" refreshMode="norefresh">
    <xp:this.script><![CDATA[console.log('Page - onClientLoad');]]></xp:this.script>
    <xp:this.action><![CDATA[#{javascript:print('Page - onClientLoad');}]]></xp:this.action>
  </xp:eventHandler>

  <xp:panel id="panel1">
    <xp:eventHandler event="onClientLoad" submit="true" refreshMode="norefresh">
      <xp:this.script><![CDATA[console.log('Panel 1 - onClientLoad');]]></xp:this.script>
      <xp:this.action><![CDATA[#{javascript:print('Panel 1 - onClientLoad');}]]></xp:this.action>
    </xp:eventHandler>
  </xp:panel>
  
  <xp:panel id="panel2">
    <xp:eventHandler event="onClientLoad" submit="true" refreshMode="norefresh">
      <xp:this.script><![CDATA[console.log('Panel 2 - onClientLoad');]]></xp:this.script>
      <xp:this.action><![CDATA[#{javascript:print('Panel 2 - onClientLoad');}]]></xp:this.action>
    </xp:eventHandler>
  </xp:panel>

</xp:view>

Here’s what happens when the page loads:

  1. The page-level client-side code runs to write the message to the browser console
  2. The page-level server-side code runs to write the message to the server console

That’s it.

Execution on onClientLoad handlers stops at this point because the page has posted to the server because of the server-side code; the onClientLoad event handlers for the panels do not execute at all.

You can look in the browser’s developer tools and see the POST that is sent. It doesn’t try to update anything on the page, so there’s no response, but it’s still enough to interrupt everything else.

More details on the behavior:

  • You can run multiple client-side onClientLoad event scripts without issue — as long as they’re not triggering partial refreshes
  • If the event handlers are rearranged on the page, the one that comes first in the source is the one that will run
  • If there is only one onClientLoad event with server-side code, it will run (based on it’s order in the source), then POST, then let the rest of the onClientLoad events on the page that only have client-side code run. (Any with server-side code will not execute — even the client-side event code on handlers that also have server-side code will not run.)

Mitigating the Problem

This may sound like an easy thing to keep in check, but if you have onClientLoad code on multiple custom controls, it may be hard to make sure that there won’t be a conflict, especially because there won’t be any error thrown.

The strong recommendation is to just not put server-side code in the onClientLoad event, period.

If you absolutely need it, then you may need a standard of putting any necessary onClientLoad code in the common layout control that’s loaded on every page, but it still has the potential to interrupt anything else going on the page or annoy the user by causing a delay immediately after the page is loaded waiting for the post and response. I would try to put code in the afterPageLoad event or use client-side code to trigger an RPC method or custom REST service if server-side code needs to run in that event.

Handling Errors in an XPages RPC Method

The Remote Service (aka xe:jsonRPCService) is an extremely useful control in XPages because it examples client- and server-side code interaction, runs asynchronously, performs well, and is easy to use. But if you don’t handle errors well, it can fail quietly and the user may never know. In this post, I’ll show how to handle client- and server-side JavaScript errors with an RPC method.

Remote Procecure Example

As a starting point, let’s look at an example of a simple RPC method that returns the date and time and displays it for the user.

RPC Control

Here’s the XPages component source:

<xe:jsonRpcService id="jsonRpcService1" serviceName="myRPCService">
  <xe:this.methods>
    <xe:remoteMethod name="myMethod" script="return myLibraryFunction();">
    </xe:remoteMethod>
  </xe:this.methods>
</xe:jsonRpcService>

The service name is myRPCService. The method, myMethod takes no parameters and just calls an SSJS library function named myLibraryFunction().

SSJS Library Function

Here’s the SSJS function that it calls:

function myLibraryFunction() {
  var d = new Date();
  return d.toLocaleString();
}
Client-Side JavaScript

Here’s the code that calls the RPC method:

myRPCService.myMethod().addCallback(function (response) {
  alert('response: ' + response);
})

It calls the method in the RPC service and attaches a callback function to wait for the response. It then displays the response to the user in an alert.

RPC Error Handling - A

Handling Client-Side Errors

This works well and RPC functionality is awesome, but you have to be careful about how you handle errors.

Take for example, this problematic update. This code ignores the response, but tries to display an invalid value (an undeclared variable) in an alert.

myRPCService.myMethod().addCallback(function (response) {
  console.log('before');
  alert ('Invalid variable: ' + myUndeclaredVariable);
  console.log('after');
})

When you run the code, it appears that nothing happens. The console shows the ‘before’ statement, but it then fails quietly.

Putting a try-catch block around it like this doesn’t help at all.

try {
  myRPCService.myMethod().addCallback(function (response) {
    console.log('before');
    alert ('Invalid variable: ' + myUndeclaredVariable);
    console.log('after');
  })
} catch (e) {
	alert('Error: ' + e.toString());
}

This may seem a bit confusing, but it actually makes sense. The callback effectively runs in its own scope, so the error handling needs to be within the callback in order to fire when dealing with an RPC call.

This code does the trick:

myRPCService.myMethod().addCallback(function (response) {
  try {
    console.log('before');
    alert ('Invalid variable: ' + myUndeclaredVariable);
    console.log('after');
  } catch (e) {
    alert('Error: ' + e.toString());
  }
})

RPC Error Handling - B

Handling Server-Side Errors

If there’s an unhandled exception on the server-side code that runs (such as an error or forgetting to return a response), it also fails quietly, although it will show a few errors in the browser console.

RPC Error Handling - C

If you expand the POST, you’ll see that it returns a stack trace for an RPC error.

In this case, error handling in the client-side callback function doesn’t help at all, so it’s important to handle the error in the server-side code and return something that the callback can deal with.

function myLibraryFunction() {
  try {
    print (myUndeclaredVariable);
    var d = new Date();
    return d.toLocaleString();	
  } catch (e) {
    return 'Error: ' + e.toString();
  }
}

RPC Error Handling - D

Now, I’m returning a more informative message to the user about what went wrong.

I generally like to set up my server-side methods to return an object to the callback function, because that way I can add as many properties as I want in order to return all of the information that I need. I generally include a property for a boolean value for whether the method was successful and another value that contains a success or error message. In the callback, those values can be checked and appropriate action can be taken accordingly.

MWLUG Slides – AD114 Take Your XPages Development to the Next Level

In this session at MWLUG 2015, Paul Calhoun and I dug into a number of features already available in XPages, but not as widely used.

We only had time to cover half of the sections, but the full set of slides is available here

Topics include:

  1. Application Responsiveness (primarily JSON RPC)
  2. SSJS Tips
  3. Modifying Component output
  4. Java Tips
  5. Custom Controls (including dynamic field binding to make reusable fields)
  6. Resources for learning more
  7. Debugging Tips
  8. Event handlers
  9. Dojo

Including a Java Class in SSJS

You’re most likely aware that SSJS really is really turned into Java under the covers, but did you realize that you can use Java classes in SSJS? In this post, I’ll show how with a simple example.

Using a Java class in SSJS

There are examples similar to this in the DDE help when looking up how to get a document from a view:

var vec = new java.util.Vector();
vec.addElement('firstLookupKey');
vec.addElement('secondLookupKey');
var doc - myView.getDocumentByKey(vec);

And so on. Several view lookup methods take a vector as a way to look up documents or view entries with multiple keys for multiple sorted columns.

The first line specifies that it’s creating a new java.util.Vector() object. After that, you can use methods of that Java class right from SSJS.

Using Type Ahead Autocompletion for Java Class Methods

If you specify the type of the variable that you create, Domino Designer provide type ahead. Take this example:

var vec:java.util.Vector = new java.util.Vector();

Just type the variable name and a period, and (maybe after a slight pause), you’ll see a list of properties and methods to choose from:

Java Class in SSJS

Importing a Java Class into SSJS

If you want to refer to an object more easily in SSJS, you can import the class you want to use and then reference it just by the class name, rather than by the fully-qualified name. To do this, use importPackage().

Here’s how this would look different when working with a Vector:

importPackage (java.util.Vector);	
var vec = new Vector();

And now you have code that’s a little easier to follow and also has the ability to display the properties and methods of the class:

Java Class in SSJS 2

Clearly, there is far more opportunity to use this than the simple examples I’ve shown here, but I hope this gets the gears turning and becomes a useful feature for you.

XPages Tip: Using _dump to write information to the server console

I recently showed how to use a built-in client-side JavaScript function to dump the contents of an object for review. In this post, I’ll show a somewhat similar method available in Server-Side JavaScript.

_dump() is a global method available in SSJS. You can use it to write the contents of an object out to the server console (and log).

The XPages Portable Command Guide mentions using it to dump the contents of scope variable maps.

Since the requestScope automatically has a bunch of properties, I can see the results if I run this beforePageLoad:

_dump(requestScope);

Here is the output on the server console — basic URL, user, server, and page info:


HTTP JVM: com.sun.faces.context.RequestMap
HTTP JVM: =
HTTP JVM: {database=BlogTesting.nsf, __xspconvid=null, opensession=CN=XDev9/O=Xcellerant, userScope={}, session=CN=XDev9/O=Xcellerant, cookie={SessionID=javax.servlet.http.Cookie@7190719}, component
HTTP JVM: arameters=com.ibm.xsp.application.ComponentParameters@3f953f95, context=com.ibm.xsp.designer.context.ServletXSPContext@56fc56fc,
identityScope={}, serverScope={Anonymous={}}, opendatabase=BlogTesting.nsf, com.ibm.xsp.SESSION_ID=B7117B555E750057C2668FD5A46
HTTP JVM: 51CB288B7621}

The sessionScope is empty by default, but I can add a few properties and then look at the output:

sessionScope.property1 = 'abc';
sessionScope.property2 = 'def';
_dump(sessionScope);

HTTP JVM: com.sun.faces.context.SessionMap
HTTP JVM: =
HTTP JVM: {__XSP_STATE_FILE=com.ibm.xsp.application.FileStateManager$SessionState@70647064,
__notescontext_publicaccess=com.ibm.domino.xsp.module.nsf.NotesContext$AccessPrivileges@2a0b2a0b, property2=def, xsp.sessionData=com.ibm.xsp.designer.context.PersistentSessi
HTTP JVM: nData@34fb34fb, property1=abc}

Not exactly easy to read, but the information is there.

But you’re not limited to scope variables. Here’s an example of creating my own object and writing it out:

var myObject = {};
myObject.foo = 'xyz';
myObject.bar = 'uvw';
_dump(myObject);

HTTP JVM: Object
HTTP JVM: =
HTTP JVM: [object Object]
HTTP JVM:
HTTP JVM: +-
HTTP JVM: foo
HTTP JVM: :
HTTP JVM: string
HTTP JVM: =
HTTP JVM: xyz
HTTP JVM:
HTTP JVM: +-
HTTP JVM: bar
HTTP JVM: :
HTTP JVM: string
HTTP JVM: =
HTTP JVM: uvw

This seems ridiculously spread out, but it’s pretty easy to see the contents of the object.

All in all, MarkLeusink’s Debug Toolbar is a much nicer tool to use when working with scope variables, but if you’re doing some quick debugging — or setting up a consistent method of handling errors, the _dump() method can come in handy.

Getting the Base URL of the Current Database with SSJS

It’s easy to get the current page URL in SSJS with context.getUrl().toString(). However, it’s a little less straightforward if you just want to determine the base URL of the current application. This can be useful when you need to build a link to some other page within the current application and send it out in an e-mail notification. In this post, I’ll look at the results of some methods in the XSPUrl object and show how to achieve the desired effect.

In the last post, I looked at URL @functions in the Extension Library. There are some useful functions there, but none that retrieve what I’m looking for.

XSPUrl

The global context object has a getUrl() method that makes it easy to get a handle to an XSPUrl object representing the current URL. You can retrieve the full URL (including the querystring) or use any of the methods available to get pieces of the URL ( getAddress(), getHost(),getParameter(), getPath(), getPort(), getQueryString()).

Here are some examples of the output, given this url: http://127.0.0.2/BlogTesting.nsf/URL.xsp?parameter1=a

Method Result
context.getUrl().toString() http://127.0.0.2/BlogTesting.nsf/URL.xsp?parameter1=a
context.getUrl().getPath() /BlogTesting.nsf/URL.xsp
context.getUrl().getAddress() http://127.0.0.2/BlogTesting.nsf/URL.xsp
context.getUrl().getHost() 127.0.0.2
context.getUrl().getSiteRelativeAddress(context) /URL.xsp

There are several handy methods, but none providing exactly what I’m looking for.

Options for Getting the Base URL

The getAddress() method gets the URL up to, but not including, the querystring. However, it still includes the current page name. The global view object has a getPageName() method that returns the name of the currently-displayed XPage.

The snippet that I’ve been using recently uses the getAddress() method and replaces the current page name with an empty string, leaving us with the base URL:

context.getUrl().getAddress().replace(view.getPageName(), '')

You could just as easily use Javascript’s substr or substring methods to look for the ‘.nsf’ and only retrieve characters up to the end of it in most cases, but that may be problematic if you have routing that masks the NSF name.

What’s Your Solution?

This seems like something that probably has a number of workarounds. How have you solved it? Is there something simpler that I’ve overlooked?

Update

Check out David Leedy’s XPages URL Cheatsheet for more URL-related functions http://xpagescheatsheet.com/cheatsheet.nsf/url.xsp

There are two listed on there that sounded like they might provide what I was looking for, but did not.

  • database.getHttpURL() – includes the database replica ID rather than name and appends ?OpenDatabase to the end
  • view.getBaseURL() – sounds perfect, but doesn’t return anything