XPages Tip: Component API Documentation

I’m sure you’ve seen the standard XPages Reference that’s part of the Domino Designer help, but you may not be aware that there’s also API documentation for XPages controls that’s available. It’s documentation of the API for the underlying Java classes and it can be very useful in letting you know what methods are available for each component.

Here’s an example for an Input Text control. At the top of the page, you can see the class hierarchy that leads to the component.

ControlDocs1_ClassHierarchy

This gives you the ability to also find methods that are available to higher-level classes from which this component inherits.

You can scroll down to see a summary of the methods available.

ControlDocs2_MethodSummary

If you scroll further, you can get a more detailed description of the methods.

ControlDocs3_MethodDetail

It can be helpful to peruse the list — you may come up with ideas to work with the component in ways that you didn’t realize were available because there isn’t a built-in property in the UI!

The API for standard components can be found in the XPages Components documentation. Do yourself a favor and keep the link handy — it’s not exactly easy to find.

In addition to the XPages component documentation, you can also go up a level in the API and see documentation for many more classes in the runtime.

I had a bookmark for the same info but with a much nicer interface on the OpenNTF site, but it is no longer valid. That included the API for all xp and xe components in on place and was an excellent reference. The documentation comes with the extension library (under \Docs\ExtLib\XPages-Doc\control.html), though, so make use of it if you have that available. If you have a link to this online — please let me know and I’ll update this post.

Update: Many thanks for a quick response from Per Lausten — the link for the controls documentation on OpenNTF has been restored!

ControlDocs4_AllTogether

Localization Tip: Troubleshooting “The code for the static initializer is exceeding the 65535 bytes limit”

I came across this error message recently: “The code for the static initializer is exceeding the 65535 bytes limit” while adding supported languages to a localized XPages application. In this post, I’ll explain the cause of the error and a couple of ways to approach fixing it.

Understanding the Error

Every XPage and Custom Control is boils down to a corresponding Java class behind the scenes. You can find these .java files in Packages Explorer under Local\xsp.

Each class contains blocks of code for dealing with each component on the page as well as handling localization strings (among other things).

If this file gets too large, it throws the error message above.

The Cause

I’ve never seen this issue until recently, but when I updated a localized application to add 8 more supported languages, this error was thrown for two of the controls.

When I looked into the class files for the controls that threw the error, it was clear what was blowing up the file size — translation strings.

The file builds an array of string arrays in a variable named s_localeStrs. Below is an example of what it looks like. The first line initializes the variable and then the rest is what is set up for each for the first few things on the page that are translated. (This is from a demo app from Connect 2014.)

private static final String[][] s_localeStrs = new String[][]{
  new String[]{ // ""
    "Hard-Coded Title, Labels, Options", // 0 formTable1/@formTitle
    "Description", // 1 formTable1/@formDescription
    "Name", // 2 formRow1/@label
    "Type", // 3 formRow2/@label
  },
  new String[]{ // de
    "[de| Hard-Coded Title, Labels, Options ]", // 0 formTable1/@formTitle
    "[de| Description ]", // 1 formTable1/@formDescription
    "[de| Name ]", // 2 formRow1/@label
    "[de| Type ]", // 3 formRow2/@label
    "[de| Type 1 ]", // 4 comboBox1/xp:selectItem[1]/@itemLabel
  },
  new String[]{ // en_GB
    "[en_GB| Hard-Coded Title, Labels, Options ]", // 0 formTable1/@formTitle
    "[en_GB| Description ]", // 1 formTable1/@formDescription
    "[en_GB| Name ]", // 2 formRow1/@label
    "[en_GB| Type ]", // 3 formRow2/@label
    "[en_GB| Type 1 ]", // 4 comboBox1/xp:selectItem[1]/@itemLabel
  },
  new String[]{ // en_IE
    "[en_IE| Hard-Coded Title, Labels, Options ]", // 0 formTable1/@formTitle
    "[en_IE| Description ]", // 1 formTable1/@formDescription
    "[en_IE| Name ]", // 2 formRow1/@label
    "[en_IE| Type ]", // 3 formRow2/@label
    "[en_IE| Type 1 ]", // 4 comboBox1/xp:selectItem[1]/@itemLabel
  },
  new String[]{ // lt
    "LT-Hard-Coded Title, Labels, Options ", // 0 formTable1/@formTitle
    "LT-Description ", // 1 formTable1/@formDescription
    "LT-Name ", // 2 formRow1/@label
    "LT-Type ", // 3 formRow2/@label
  },
};

As an application is set up to support a language, it adds the string translations for each translated property directly into the class file. So if you support 10 languages, you’ll have 10 sets of strings set up. (Note: this does not happen with translations for computed values that you manage, because the application does not translate them, so it doesn’t mix anything into the page/CC class files.)

This is something you won’t even know or care about for the most part, but when you have a lot of elements on the page to be translated and numerous translations, it can make the size of this class too large, hence the error.

In the case where I saw the error, it was on a custom control that had 864 items to translate. Once the application went from 2 to 10 languages, the size of this java class file got too large.

The Solution

The bottom line is that the class file needs to be cut down to the allowable size. You can do this by breaking the custom control into smaller controls until you get to the right size; then the problem goes away.

However, since that can become quite involved, I recommend doing some analysis on the current control’s design before taking the step to break it up.

Look for ways to modularize/reuse pieces of the page. If there are repetitive controls or combo box option lists, you can look to streamline their usage in order to cut down on the amount of things that are set up for translation.

In this case specifically, there were a bunch of combo boxes on the page with a list of every available time to choose, to the quarter hour. For example: 12:00, 12:15, 12:30, 12:45, 1:00, 1:15, 1:30, and so on.

Each combo box had 48 hard-coded options and there were numerous time fields on the page. This meant that the java class file had a set of 48 strings for each of 10 languages, multiplied by the number of time selection fields on the page. And these are things that don’t need translation! (The same thing could happen for any list of values that wouldn’t need translation, such as a list of proper names like states.)

There are several ways to go about handling this. Here are a few ideas:

  1. Create the field as a reusable custom control and handle it in one place and then use it as many times as needed
  2. If the values needed translation, you could set up one list of the values in a custom properties file and refer to it throughout the application.
  3. If the values don’t need translation, the easiest way to mitigate this issue is to compute the list of choices. Instead of dozens of select item tags, compute it to return an array
  4. Set up the list of choices once in a configuration/keyword document and look it up either directly from the control
  5. Define an array in a page-level or scope variable one time and reference it for the combo box’s list of choices as many times as needed

Any of these options modularize the list of options and significantly streamline the page source. They also cut down on a lot of unnecessary elements in the properties files if you need to send them out for translation.

Gridx in XPages – 31: Saving Inline Edits

In the last post, I showed how to enable inling editing on a grid column. However, the changes made were only stored in the in-memory data store. In this post, I’ll show how to save the updated value to the back-end document.

Gridx Series

Gridx in XPages — Entire Series

General Concepts

The process will run after the change has been made. The changes will be saved asynchronously so the user isn’t blocked while waiting for the update to happen on the server.

We need a server-side method of applying the changes. In this example, I’ll be using the Domino Data Service (DDS) because it’s a little simpler. You could also create your own custom REST service to receive requests and process the changes.

Domino Data Service

DDS is a REST API that allows you to access databases on a Domino server. It can handle HTTP/S requests and JSON data.

You can use it to get database, view, folder, or document info as well as send updates. We’ll use it to save changes made in the grid.

(If your administrator won’t allow this, then you can go the route of custom REST services)

1. Enabling Domino Data Service on the Server

DDS can be enabled on the Server document (Internet Protocols tab > Domino Web Engine subtab, under the Domino Access Services section. Select Data in the Enabled Services field), but better to do on a Web Site document.

Set Enabled Services to Data under Domino Access Services and ensure that PATCH is selected under Allowed Methods (on the Configuration tab)

Gridx 31 A Enable DDS and PATCH on Web Site Doc

Note: The server document must be set to use web site documents.

2. Enabling Domino Data Service on the Database

You also need to enable it on the specific database (and views, if getting data from views, but we won’t be doing it in this example, so we only need it at the database level.)

Gridx 31 B Enable DDS on Database

HTTP PATCH

If you’re using XPages, then you’re most likely familiar with the HTTP GET and POST methods. You may assume that we’d use a POST to update a document (because that’s what happens when a form is saved), but in this case, we’ll use PATCH, because it’s more efficient.

A POST will replace the entire document with the contents that you pass whereas a PATCH will only update the specified fields in the specified document.

We need to determine the URL to which the request will be sent. In general, you’ll use a URL that’s relative to your current path. If you’re updating a document in the same database that’s currently open in the browser, then the URL to send a document update request against DDS looks like this:

/api/data/documents/unid/<documentUNID>

(Many thanks to Marky Roden for his posts with PATCH examples.)

Testing the Configuration

In order to make sure that everything is configured properly, this is a good point to test it out with a hard-coded request.

To do so, go into your database and get the UNID of a document. Also, make note of a field name to modify. Then we can use an XHR request in the browser console to test running a PATCH in order to make sure that it works (before adding the complexity of running it from the grid).

dojo.request.xhr("api/data/documents/unid/4030FB42B37B397785257D87004680BC", {
  data: '{"firstname":"Patched!"}',
  method: "PATCH",
  handleAs: "json",
  headers: {'Content-Type': 'application/json'}
}).then(function(data){
  console.log('successful');
}, function(err){
  console.log('error');
});

If it works, you’ll see the PATCH request in the browser console (or Net tab) of the browser’s dev tools:

Gridx 31 C Successful PATCH

Notes:

  • JSON requires double-quotes for properties and values
  • The data passed must be a string, so use single quotes to surround the JSON
  • handleAs must be set to “json” and the Content-Type header is also required
  • The newer Dojo xhr module is not automatically available in XPages, so you need to add it as a Dojo Module Resource
<xp:this.resources>
  <xp:dojoModule name="dojo.request.xhr"></xp:dojoModule>
</xp:this.resources>

Saving Edits in the Grid

Now that we know the server and database are configured to allow us to save changes, we can update the grid to send the proper requests.

To do so, we’ll add an event handler to the onApply event of the Edit module. This will fire whenever the user makes a change (and does not cancel it by hitting the ESC key).

The first argument that the onApply callback receives is an object that gives you access to the row, column, updated value, etc. This gives us what we need to build the proper PATCH request.

To attach a callback to the onApply event, update the grid’s modules property to include the Edit module as an object and define the onApply event callback:

{
  moduleClass: Edit,
  onApply: saveChange
}

Here’s the function that runs when a grid cell is changed and sends the PATCH request to save the changes:

function saveChange(gridObject) {
  var newValue = gridObject.rawData();
  var fieldName = gridObject.column.field();
  var rowUNID = gridObject.row.item()['@unid'];

  // Build set up the JSON object for the update
  var data = {};
  data[fieldName] = newValue;
  var jsonData = JSON.stringify(data);

  dojo.request.xhr("api/data/documents/unid/" + rowUNID, {
    data: jsonData,
    method: "PATCH",
    handleAs: "json",
    headers: {'Content-Type': 'application/json'}
  }).then(function(data){
    console.log('successfully updated document (' + rowUNID + ')');
  }, function(err){
    console.error('error updating document (' + rowUNID + ')');
  });
}

Line 2 gets the updated cell value from the grid (after any fromEditor processing is done)

Line 3 gets the name of the source field from the column. This will only work if your grid column is named after a single field. If it has a different name, then you’ll need to take that into account when sending the update.

Line 4 gets the UNID of the document from the data store. The item() method of the row provides the entire record for that row, so I’m pulling the @unid property that is provided by the REST service that supplies the data for the grid. (XPages REST services will automatically include it.)

Lines 7-9 set up an object with the data to send to update the back end document and then ensure that it’s formatted as JSON.

Lines 11-20 send the PATCH request to the server to update the back end document.

Only Send Changes if Data is Modified

The last thing I want to do now is prevent this from firing every time a cell goes out of edit mode. If the user didn’t change the data, then it’s a waste of bandwidth and server processing to send updates to the back-end document.

I don’t see a way to get the original value from the onApply callback, but you can use the onBegin event to store the value in a global variable, then check it before sending any changes.

Here’s a function I added to the onBegin callback:

function beforeEdit(gridObject) {
  window.OriginalValue = gridObject.rawData();
}

Here’s the module inclusion that adds the callback:

{
  moduleClass: Edit,
  onApply: saveChange,
  onBegin: beforeEdit
}

Now, the first few lines of saveChange() can be updated to check whether the value changed and cut out early if it hasn’t:

// Get the updated value (after any fromEditor callback is done)
var newValue = gridObject.rawData();

if (window.OriginalValue == newValue) {
  return;
}

Security

In case you’re wondering, security is based on your current Domino session. If you’re logged into the server, then any PATCH request will be sent with your ID. (If you’re not logged in, it’ll be sent as Anonymous.)

If the user does not have rights to edit the document in the NSF, then the PATCH request will return an error like this:

Gridx 31 D Error PATCH

Gridx in XPages – 30: Getting Started with Inline Editing

Like other modern grids, Gridx can provide the ability for users to edit data inline. This can very handy for making quick changes without having to leave the page or load another one to edit a form. In this post, I’ll show how to get started with editing data in the grid.

Gridx Series

Gridx in XPages — Entire Series

Making a Column Editable

To make a grid column editable, you need to include two modules and set a property in the column definition.

1. Add the ‘Edit’ and ‘CellWidget’ modules

Add the Edit and CellWidget modules to the require statement and to the grid’s modules property

"gridx/modules/Edit",
"gridx/modules/CellWidget",

...

modules: [
  Resizer,
  NestedSort,
  Filter,
  FilterBar,
  QuickFilter,
  VirtualVScroller,
  CellWidget,
  Edit
]

2. Add a Column Property

Now that the modules are in place, all you have to do us add the editable property to a column definition and set it to true.

{
  id: 'first',
  field: 'firstname',
  name: 'First',
  width: '70px',
  editable: true
},

Default Edit Behavior

Now you can change the firstname column to edit mode by either double-clicking on a cell or selecting the cell and hitting the [ENTER] key.

By default, it will provide a plain text field for you to modify the data.

Gridx 30A

To stop editing, either hit the [ENTER] key or click or tab out of the field. It then changes back to read mode and shows the updated value.

Note: This preserves the change in the in-memory data store, but does not save it to the back-end document. (I’ll dig into that more in the next post.)

Always in Edit Mode

If you’d rather the data in the column always be in edit mode, you can add the alwaysEditing property to the column definition and set it to true.

Gridx 30B

{
  id: 'first',
  field: 'firstname',
  name: 'First',
  width: '70px',
  editable: true,
  alwaysEditing: true
},

Processing the Updated Value

You can also attach a function to process the user-entered value before it is updated in the data store.

In this example, I have a function that surrounds the updated value with asterisks.

function processState(storeData, gridHandle) {
  return '*' + storeData + '*';
}

The function automatically receives two parameters (it doesn’t matter what you name them): the updated value in the cell and a handle to the grid, which also gives you access to the row and column being edited, so you can get the document ID from the row (which will be necessary when saving changes to the back end document).

To call it when the column is updated, it needs to be added to the editorArgs object via the fromEditor property.

{
  id: 'state',
  field: 'state',
  name: 'State',
  width: '40px',
  editable: true,
  editorArgs:{
    fromEditor: processState
  }
},

There is also a toEditor callback that can be used to process the data before it becomes editable (but that would be more useful with other data types and edit controls).

You can also add callbacks to the onBegin, onApply, and onCancel events as needed.

Up Next

In the next post, we’ll look at saving the changes. After that, we’ll look at different types of widgets you can use to edit data in the grid.

Gridx in XPages – 29: Programmatic API for Details on Demand

In the last post, I showed how to add an expandible detail section to each row via Gridx’ Details on Demand feature. In this post, I’ll show how use the provided methods to programmatically expand and collapse the details, as well as how to attach event handlers to run after a detail section is opened or closed.

Gridx Series

Gridx in XPages — Entire Series

Methods

In the last post, I mentioned that there’s a showExpando property of the dod module that can be used to prevent the expand/collapse icon from being displayed. If you’d rather use your own button to expand/collapse the detail row, you can set up your own column with HTML content (button, icon, etc) to do the job programmatically with the built-in methods.

You can run the built-in methods via the dod object or via the grid row, which gets an extended API after loading the dod module.

For each of the methods below, there are 4 different statements that all have the same effect. Two that run the method via the dod module and two that run it via the extended row API. In each case, there’s an example of getting a handle to the row by index (0-based) or by the row ID (which, in my case, is the NoteID of a document in my sample grid). Note that the row API method names are slightly different.

show() – Display the detail section for a row

  • grid.dod.show(grid.row(0));
  • grid.dod.show(grid.row(‘FCE’));
  • grid.row(0).showDetail();
  • grid.row(‘FCE’).showDetail();

hide() – Hide the detail section for a row

  • grid.dod.hide(grid.row(0));
  • grid.dod.hide(grid.row(‘FCE’));
  • grid.row(0).hideDetail();
  • grid.row(‘FCE’).hideDetail();

toggle() – Toggle the detail section for a row

  • grid.dod.toggle(grid.row(0));
  • grid.dod.toggle(grid.row(‘FCE’));
  • grid.row(‘FCE’).toggleDetail();
  • grid.row(0).toggleDetail();

isShown() – Check whether a detail section for a row is displayed

  • grid.dod.isShown(grid.row(0));
  • grid.dod.isShown(grid.row(‘FCE’));
  • grid.row(0).isDetailShown();
  • grid.row(‘FCE’).isDetailShown();

Events

In addition to the method to trigger or check the row state, there are two events that allow you to run logic after a detail section is shown or hidden: onShow and onHide.

Here are two simple functions that display a message in the console on the onShow and onHide events, respectively:

function dodShowCallback(){
  console.log('Show Details');
}
	
function dodHideCallback() {
  console.log('Hide Details');
}

They can be added via the onShow and onHide module properties in the grid definition (lines 21 and 22 below):

grid = new Grid(
{
  id: "my_gridX",
  cacheClass: Cache,
  store: store,
  structure: columns,
  vScrollerBuffSize:10,
  modules: [
    Resizer,
    NestedSort,
    Filter,
    FilterBar,
    QuickFilter,
    VirtualVScroller,
    {
      moduleClass: Dod,
      defaultShow: false,
      useAnimation: true,
      showExpando: true,
      detailProvider: myDetailProvider,
      onShow: dodShowCallback,
      onHide: dodHideCallback
    }
  ]
});

Slide Deck from BP105: Take Your XPages Development to the Next Level

The slides from the session that Paul Calhoun and I delivered at IBM ConnectED 2015 are now available on SlideShare.

This intermediate-level session is for anyone who has a little bit of XPages experience. In the session, we dug deeper into a number of features that are built into XPages and can help improve application responsiveness, streamline design with code reuse, and take more control over the output that is generated by XPages controls.

There was way too much content to fit into a single session, but the slide deck includes all of the extra material that was prepared.

Outline:

  1. Application Responsiveness
  2. SSJS
  3. Modifying Component Output
  4. Java
  5. Custom Controls
  6. Debugging Tips
  7. Event Handlers
  8. Dojo

Gridx in XPages – 28: Expandable Details on Demand

The Details on Demand module of Gridx allows you to expand a row and show additional related information, much like the detail facet of an XPages Data View. This gives you a lot of flexibility to add custom content related to the row, but without requiring it to be visible by default. You could display more information about the current row, look up related information to display, draw a chart, provide a form, etc. In this post, I’ll show how to add it to the grid and set up a simple example.

Gridx Series

Gridx in XPages — Entire Series

Details on Demand

The details are not displayed or loaded initially (hence the “on demand” part of the name). As you expand a row, it executes logic that you provide to create the content for the detail section.

By default, the looks virtually identical even after the Dod module is included; you can see below that the data in the first column now has a bit of a left margin.

When you hover anywhere over the row, you see the expando icon appear on the left.

Gridx 28A

If you click on the icon, it will expand the row to show the additional content.

Gridx 28B

Implementation Overview

Since custom content is involved, the Details on Demand feature requires more work that most Gridx modules. As with all modules, you need to require the proper module and add it to the grid, along with a few properties. However, you also need to provide two functions to generate the content to display when a row is expanded.

1. Require the Dod module in the grid

"gridx/modules/Dod"

2. Add the module to the grid and set properties

In this example, it’s using the object syntax to add the module to the grid, so several properties of the Dod module can be set together.

grid = new Grid(
{
  id: "my_gridX",
  cacheClass: Cache,
  store: store,
  structure: columns,
  vScrollerBuffSize:10,
  modules: [
    Resizer,
    NestedSort,
    Filter,
    FilterBar,
    QuickFilter,
    VirtualVScroller,
    {
      moduleClass: Dod,
      defaultShow: false,
      useAnimation: true,
      showExpando: true,
      detailProvider: myDetailProvider
    }
  ]
});
  • defaultShow is a boolean value for whether to automatically expand each row
  • useAnimation is a boolean value for whether to use a sliding animation when the row is expanded and collapsed
  • showExpando is a boolean value for whether to show the expand/collapse icon in the first cell of the row (you could leave it out and expand/collapse rows programmatically)

3. Set up the content provider

The detail provider function is called from the detailProvider attribute of the Dod module when added to the grid.

It receives handles to the grid and the detail node to populate, along with the ID of the row that was expanded. (This can be used to get other data from the same row in the store.)

The function calls another function that you will use to actually put content into the detail node.

function myDetailProvider (grid, rowId, detailNode, rendered) {
  getDetailContent(rowId, detailNode);
  rendered.callback();
  return rendered;
}

It uses a Deferred object to display the detail row when the content-providing function is ready.

4. Add Function to Provide Detail Content

In this example, I’ve passed the rowId and detailNode to the function to provide content. (The detailNode is required — anything else is optional.)

This example simply displays a div with the row ID and some text (as shown in the screen shot above).

Since it is provided a handle to the detail node, it populates it via the innerHTML property of the node.

function getDetailContent(rowId, detailNode){
  detailNode.innerHTML = '<div style="margin:20px;">Row ' + rowId + ' detail here</div>';
}

Modify this function however you need to in order to build the content that you need.

Follow

Get every new post delivered to your Inbox.

Join 64 other followers