Archive | Gridx RSS for this section

IBM Connect Presentation Uploaded

At Connect last week, I presented a session on using grids in XPages with Paul Calhoun. Here’s a link to the slide deck

AD-1207: The Grid, the Brad, and The Ugly: Using Grids to Improve Your Applications

Do you want better features, better performance, and a better UI in your XPages applications? Then display your data in grids instead of built-in controls. In this session, Paul and Brad will demonstrate why grids are a significant improvement in general and cover why one size does not fit all. They will review the features of commonly-used JavaScript grid frameworks (including Dojo, jQuery, Kendo UI, and Sencha) in order to help you determine which is the best fit for your applications.

Gridx in XPages – 33: Adding a Column Header Menu

Another handy feature that you can add to Gridx is a popup menu from the column headers. In this post, I’ll show how to build a menu and add it to the grid.

Gridx 33 A - Header Menu

Gridx Series

Gridx in XPages — Entire Series

Include the HeaderMenu Module

As always, you have to include the required gridx module (gridx.modules.HeaderMenu) and add it to the grid object’s modules list.

modules: [
  Resizer,
  NestedSort,
  Filter,
  FilterBar,
  QuickFilter,
  VirtualVScroller,
  Persist,
  HeaderMenu
]

Build the Menu

Create a new dijit.Menu object to build the menu. You don’t need to add any resources to the page because, even though dijit.Menu and dijit.MenuItem are not automatically made available in XPages, they are made available by Gridx.

This code creates a menu and adds 3 items to it. Each item has a simple onClick handler that displays an alert.

  var headerContextMenu = new dijit.Menu();
  headerContextMenu.addChild(new dijit.MenuItem({
    id: 'menu1',
    label: 'Option 1',
    onClick: function() {
      alert('Clicked menu item: ' + this.label);
    }	
  }));
  headerContextMenu.addChild(new dijit.MenuItem({
    id: 'menu2',
    label: 'Option 2',
    onClick: function() {
      alert('Clicked menu item: ' + this.label);
    }	
  }));
   headerContextMenu.addChild(new dijit.MenuItem({
    id: 'menu3',
    label: 'Option 3',
    onClick: function() {
      alert('Clicked menu item: ' + this.label);
    }	
  }));

Add Menu to Column Definition

Now that the menu is ready, all you have to do is add it to one or more columns via the menu attribute.

var columns = [
  {
    id: 'first',
    field: 'firstname',
    name: 'First',
    width: '70px',
    menu:headerContextMenu
  }
]

You can add the same menu to all columns or you can create different menus for different columns.

Selecting a Menu Option

When you hover over a column header of a column that has a menu, a solid downward-facing black triangle is visible on the right-hand side of the column header. (Note: The mouse cursor isn’t in the screen shot, but it was hovered over the column header.)

Gridx 33 B - Menu Icon

Here’s the strange part — the interface is not intuitive; left-clicking on the triangle does nothing but select the triangle image.

The menu will be displayed when your right-click on the triangle. (See screen shot near beginning of the post.) Then you can left-click on a menu option to select it.

Gridx 33 C - Alert

Gridx in XPages – 32: Editable Cell Widgets

In the last couple of posts in this series, I showed how to edit data inline and save the changes. But you’re not limited to plain text fields. In this post, I’ll show how to make cells editable with additional input widgets.

Gridx Series

Gridx in XPages — Entire Series

Adding Input Widgets

There are generally 3 steps to the process:

  1. Include the CellWidget module
  2. Include your chosen input widget module (if necessary)
  3. Set the column properties to include and configure the widget

To add any kind of widget for inline data input, you need to first required the CellWidget module and add it to the grid.

To do so, include "gridx/modules/CellWidget" in the require statement (or at the page level) and then also include it in the grid’s modules list.

The next two steps will be specific to the widget that you choose. Below are examples of using a combo box, date picker, and number spinner.

Combo Box

The dijit.form.ComboBox module is automatically included within XPages, so you do not need to require it.

In this example, I’ve added it to the state column. Here’s the updated column definition:

{ id: 'state', field: 'state', name: 'State', width: '80px', 
  editable: true, 
  editor: 'dijit.form.ComboBox',
  editorArgs:{
    props:'store: stateStore'
  } 
},
  • The editable property tells the grid that the column is editable
  • The editor property tells the grid what widget to use when editing
  • The editorArgs property is an object that includes properties required to configure the editor

The ComboBox requires a memory store with the list of choices, so that needed to be set up earlier in the code.

It uses the same MemoryStore module that’s already included for the grid to use. Each option must have a name and unique id.

Note: Do not declare the memory store object as a var. This causes the grid to not be able to find it.

stateStore = new MemoryStore({
  data: [
    {name:"FL", id:"FL"},
    {name:"GA", id:"GA"},
    {name:"IL", id:"IL"},
    {name:"NJ", id:"NJ"},
    {name:"PA", id:"PA"}
  ]
});

Now, when a cell in the State column is put into edit mode, there is a combobox with a list of choices to choose from.

Gridx 32 - ComboBox Widget

Date Picker

If you want to let the user change a date value, it is very handy to include a date picker.

Here are the properties that I added to the date column in order to enable a date picker in my example:

  editable: 'true',
  editor: 'dijit.form.DateTextBox',
  editorArgs: {
    fromEditor: storeDate
  }

I didn’t have to require the dijit.form.DateTextBox module because it’s available already within XPages.

Since it’s a date field, I needed to add a fromEditor function to parse the data before it’s stored. (See this post on formatting date columns for more information on date parsing.)

This function will return a date string that works with the formatting code already on the column:

function storeDate(storeData, gridHandle) {
  if (typeof storeData == 'object') {		
    return storeData.toLocaleDateString();
  } else {
    return '';
  }
}

Gridx 32 - DatePicker Widget

Number Spinner

For the last example, I added a NumberSpinner to a cell. This module is not automatically available in XPages, so it needs to be included.

One of the ways to do that is at the page level with a Dojo Module Resource:

<xp:this.resources>
  <xp:dojoModule name="dijit.form.NumberSpinner"></xp:dojoModule>
</xp:this.resources>

Now that the module is available to the page, it can be used for cell input.

In this example, I have a Rating column that has a number spinner that allows values from 1 to 5.

{ id: 'rating', field: 'Rating', name: 'Rating', dataType: 'number', width: '75px', 
  editable: true, 
  editor: 'dijit.form.NumberSpinner',
  editorArgs:{
    props: 'smallDelta: 1, constraints: {min:1, max:5, places:0}'
  } 
} 

Gridx 32 - NumberSpinner Widget

There are many more options; hopefully, these examples make it much easier to implement any others that you need.

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
    }
  ]
});

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.