Archive | Dojo RSS for this section

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

Advertisements

XPages Tip: Setting the Body Class

If you need to set a class on the body tag of an XPage, you can do so via a page property. In this post I’ll show how to set it and the effect that it has on an XPage and Custom Control.

Default Body Class

If you create a blank XPage and display it in a browser, you’ll see that the body tag gets two classes by default:

<body class="xspView tundra">

Dojo generally looks for a theme name in the body class and tundra is the Dojo theme that XPages uses by default.

If you want to change this, you can do it application-wide via a theme or you can change the class of the body tag on a single page.

Adding a Body Class

You can change the body class by selecting the xp:view tag, going to All Properties > styling > styleClass

BodyClass

This changes the body tag sent to the page to be this:

<body class="myNewClass xspView tundra">

Note 1: I’ve seen other cases where it removed existing body classes and only used the one specified in the page property. I would guess this has to do with the theme in use, but I haven’t tested it further. Just look at the page output before and after to see the effect.

Note 2: If this messes up styling of standard XPages controls, you can add multiple classes — just separate each one with a space like you would when adding multiple classes to any HTML element.

Setting the styleClass of a Custom Control

The same property is available on a custom control. However, as you might expect, the effect is different, since the page only has 1 body tag.

If you set the property on a custom control, it leaves the page’s body tag alone but rather adds a div with that class to surround the contents of the custom control.

<body class="xspView tundra">
...
<div class="myClass">
</div>

dGrowl Redux – Using a Dojo Module Path Resource to Include the Library

In light of recently figuring out how to use a Dojo Module Path Resource to make a Dojo library available in an XPages application, I wanted to revisit the way I’ve been including dGrowl. In this post, I’ll show how to include it in a cleaner way.

dGrowl in XPages

dGrowl is a third party Dojo plugin that provides growl-style messages that are much nicer than Dojo Toaster.

This episode of NotesIn9 explains how to implement dGrowl in XPages and this post shows how to add two more notification styles.

Module Inclusion – Take 1

To use dGrowl (after adding the files to the application’s WebContent folder), you need to include a stylesheet and include the module in the application.

The original method I used (based on a trick found in Mastering XPages) was to add a script resource and build out the path to the main.js file, relative to the current application.

<xp:this.resources>
  <xp:script clientSide="true">
    <xp:this.contents><![CDATA[
      var pathParts = location.pathname.split('/');
      pathParts.pop();
      dojo.registerModulePath("dGrowl", pathParts.join('/') + "/dGrowl/main");			
    ]]></xp:this.contents>
  </xp:script>

  <xp:dojoModule name="dGrowl"></xp:dojoModule>
  <xp:styleSheet href="/dGrowl/dGrowl.css"></xp:styleSheet>
</xp:this.resources>

Lines 4-6 build the path to the script file and register it via dojo.registerModulePath().

Line 10 includes the module on the page.

Module Inclusion – Take 2

Now that I’ve seen how to use a Dojo Module Path Resource, this can be simplified. This resource executes the same Dojo method to register a module path.

Using this, I can dramatically simplify the page resources as follows:

<xp:this.resources>
  <xp:dojoModulePath url="/dGrowl/main" prefix="dGrowl"></xp:dojoModulePath>
  <xp:styleSheet href="/dGrowl/dGrowl.css"></xp:styleSheet>
</xp:this.resources>

The Dojo Module Path Resource just needs a URL relative to the WebContent folder of the current application. The prefix defines how to refer to the resource.

Using AMD loading, I can now initialize the dGrowl object like this:

<script type="text/javascript">
var dg;
 	
require([
  "dGrowl",
  "dojo/domReady!"
], function(dGrowl) {
 	
  dg = new dGrowl({'channels':[
    {'name':'error', 'pos':1},
    {'name':'info', 'pos':2}
  ]})
});
</script>

Line 2 declares a global variable that I can use anywhere on the page to load a dGrowl message.

Line 5 includes the module (using the name I gave it in the module resource path prefix) and line 7 makes it available to the following function under the name dGrowl.

Now, I can display dGrowl messages from anywhere on the page the same way I did in the other examples. Here’s an example that will display an informational message:

dg.addNotification('Here is an informational message',{'channel':'info', 'duration': 3000});

That’s it. This is definitely a much cleaner way to make the code available on the page.

Gridx in XPages – 5: Modularizing Common Resources

The second post in this series showed how to use the djConfig property to make the Gridx (or any other library’s) resources available to an XPage. Looking forward, I wanted to find a way to modularize the common resources required by Gridx so they do not need to be included on every XPage. In this post, I’ll describe the challenges and then show a much cleaner solution that I found that makes it easy to reuse the resources.

Gridx Series

Gridx in XPages — Entire Series

Modularization Challenges

I thought it would be pretty straight forward to move the style sheets, djConfig property, and beforePageLoad code to define the modulePath to a custom control and reuse it across any pages that display a Gridx grid. Unfortunately, it wasn’t that simple.

The style sheets and the beforePageLoad code worked fine from a separate custom control, but the djConfig property only worked when on the XPage. This would still be an improvement in reusability, but would require setting that property on all XPages that display a grid.

I then thought that I could set the djConfig property in the application’s XSP properties. While that works for other djConfig attributes, it does not work for modulePaths because it still has the same escaping problem that necessitated the beforePageLoad code to begin with.

Dojo Module Path Resource

Fortunately, I found a much simpler solution – a Dojo Module Path Resource!

It’s a page-level resource, just like a style sheet, script library or Dojo module. It makes it much simpler to reference the Gridx code within the application.

  1. From an XPage or custom control, go to the Properties view and select the Resources subtab.
  2. Click the Add… button and select Dojo Module Path Resource…
  3. Enter gridx as the prefix and /gridx as the url

Gridx 5 - Dojo Module Resource

This tells the page to look for a module named gridx at the location /gridx. The forward slash starts from the WebContent folder in the application and we put the code into a folder named gridx. (Adjust as needed if your directory structure is different.)

This is much simpler; it removes the need for the djConfig property and the beforePageLoad code!

Common Gridx Resources

Unlike the djConfig property, specifying a Dojo Module Resource Path works from a custom control included on a page, so now we can separate the common resources into a custom control for easy reuse.

Now, I have a custom control named ccGridxResources. This is the full source:

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

  <xp:this.resources>
    <!-- Claro Theme -->
    <xp:styleSheet href="/.ibmxspres/dojoroot/dijit/themes/claro/claro.css"></xp:styleSheet>
    <xp:styleSheet href="/.ibmxspres/dojoroot/dijit/themes/claro/document.css"></xp:styleSheet>

    <!--  Module Path to locate Gridx Library -->
    <xp:dojoModulePath url="/gridx" prefix="gridx"></xp:dojoModulePath>

    <!-- -Main GridX Stylesheet  -->
    <xp:styleSheet href="gridx/resources/claro/Gridx.css"></xp:styleSheet>
		
  </xp:this.resources>
	
</xp:view>

Line 10 is the Dojo Module Path Resource that specifies where to find the files. The rest are the 3 stylesheets that we were already including. The djConfig property and beforePageLoad code are no longer required.

Streamlined XPage to Create the Grid

Now, this is the full source of the XPage required to create the same grid as the last post, accessing live data via a REST service.

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

<xc:ccGridxResources></xc:ccGridxResources>

<div id="gridX_Here" style="height:300px; width:300px;"></div>

<script>
  require([
    "gridx/Grid", "dojo/store/JsonRest",
    "gridx/core/model/cache/Async", "dojo/domReady!"
  ], function(Grid,JsonRest, Cache) {


    var store = new	JsonRest({
      idProperty: '@noteid',
      target: "X_REST.xsp/gridData"
    });

    var columns = [
      {id: 'first', field: 'firstname', name: 'First', width:	'70px'},
      {id: 'last', field: 'lastname', name: 'Last'},
      {id: 'state',field: 'state', name: 'State', width: '40px'}
    ];

    grid = new Grid({
      id: "my_gridX",
      cacheClass: Cache,
      store: store,
      structure: columns
    });

    //Put it into the DOM tree.
    grid.placeAt('gridX_Here');
    grid.startup();

  });
</script>
</xp:view>

Now we have a more modular design and a simpler page structure to work with as we get ready to add features to the grid.

Gridx in XPages – 4: Loading Live Data via a REST Service

In the last post, I showed how to get a simple Gridx grid up and running in an XPages application with hard-coded data. In this post, I’ll show how to load live data via a REST service, which requires a different type of data store and cache.

Gridx Series

Gridx in XPages — Entire Series

Providing the Data via a REST Service

My test application uses a subset of data from the Fakenames.nsf database from David Leedy’s XPages Cheatsheet.

I created an XPage named X_REST and added a viewJsonService REST service to surface data from the ByName-First view.

<xe:restService id="restJsonService" pathInfo="gridData">
  <xe:this.service>
    <xe:viewJsonService defaultColumns="true" viewName="ByName-First" var="dataRow">
    </xe:viewJsonService>
  </xe:this.service>
</xe:restService> 

The defaultColumns attribute is set to true, so it will automatically include all columns from the view in the output.

The pathInfo attribute is set to gridData, so I can reference the REST service from within the application via this URL: X_REST.xsp/gridData

Here’s a sample of the REST service output (the first row from the view):

{
  "@entryid":"1-4030FB42B37B397785257D87004680BC",
  "@unid":"4030FB42B37B397785257D87004680BC",
  "@noteid":"FCE",
  "@position":"1",
  "@siblings":1298,
  "@form":"fUserName",
  "firstname":"Adam",
  "lastname":"Gonzales",
  "address":"392 Shinn Street",
  "city":"New York",
  "state":"IL",
  "zip":10021,
  "country":"US",
  "address":"392 Shinn Street",
  "password":"nae1nae7Su",
  "phone":"212-774-8218",
  "mothersmaiden":"Shah",
  "birthday":"1952-02-08T11:53:39Z",
  "cctype":"MasterCard",
  "ccnumber":5436047424022667,
  "cvv2":830,
  "ccexpires":"2\/2009",
  "national":"051-68-8261",
  "ups":"1Z 82F 8A5 82 1526 912 5",
  "occupation":"Substance abuse and behavioral disorder counselor"
}

All of the attributes beginning with @ are system columns that are automatically included. (You can refine which system columns are included via the systemColumns attribute of the REST service.)

The rest of the attributes are based on columns in the view.

Updated Gridx Using Live Data

In order to use live data, there are two primary changes to make to the grid code:

  1. Data Store
  2. Cache

Rather than using a Memory store, it changes to a JsonRest store. This includes the ability to retrieve remote data.

Since it is now using remote data (rather than hard-coded local data), we also switch the cache from synchronous to asynchronous. All you have to do is load a different cache module — no other code needs to change.

<script> require([
  "gridx/Grid", "dojo/store/JsonRest",
  "gridx/core/model/cache/Async", "dojo/domReady!"
  ], function(Grid, JsonRest, Cache) {
	  	
  var store = new JsonRest({
    idProperty: '@noteid',
    target: "X_REST.xsp/gridData"
  });
		
  var columns = [
    {id: 'first', field: 'firstname', name: 'First', width: '70px'},
    {id: 'last', field: 'lastname', name: 'Last'},
    {id: 'state', field: 'state', name: 'State', width: '40px'}
  ];
	
  grid = new Grid({ 
    id: "my_gridX", 
    cacheClass: Cache, 
    store: store, 
    structure: columns 
  });
			
  //Put it into the DOM tree. 
  grid.placeAt('gridX_Here');
  grid.startup();
	  
});
</script>

Lines 1-4 define our updated AMD loading requirements. The data store is now dojo/store/JsonRest and the cache is now gridx/core/model/cache/Async.

Lines 6-9 set up the new JsonRest store. The target attribute specifies the URL where the REST data can be retrieved. (I’ll come back to the idProperty attribute momentarily.)

Lines 11-15 define the columns for the grid. The field attribute of each column must match up with an attribute name in the REST data. In this case, it’s set up to show the first name, last name, and state columns from the underlying view.

Lines 16-25 define the Gridx object and instantiate it. None of these lines changed since the first simple example.

Using this code, we now have a Gridx grid that displays live data from our application.

Gridx 4 - Grid

Define the Unique ID

I want to call attention to line 7 in the source code above — the idProperty attribute of the JsonRest store.

Gridx requires that each row have a unique identifier. It assumes that it will find an attribute named id in each row. However, the ByName-First view does not have a unique ID column named id. This causes the grid to display data from the last row repeatedly in place of each row in the grid (presumably because all rows have no known id and, therefore, cannot be differentiated.)

The simple solution to this is to set the idProperty attribute of the store. This lets the store know how to uniquely identify each row and it allows the grid to display the data properly. I used the @noteid attribute in the example above, but you could also use @entryid, @unid, or even @position.

Data Requests

It’s interesting to note that (without me doing any configuration) the grid makes two GET calls as the page loads.

It initially loads the first 99 rows, presumably to let the grid load faster.

It then makes a second call to get the rest of the data from row 100 on. (At this point, my example has about 1300 rows. I don’t know at this point if it will break very large data sets into multiple additional calls.)

Gridx touts performance and its ability to handle large data sets. I would assume this means that it’s doing its best to both load quickly and still get the full data set locally for fast processing.

When the grid loads, I have the ability to quickly scroll through all rows.

Gridx in XPages – Creating a Simple Grid

In the last post, I showed how to make Gridx available in an XPages application. In this post, I’ll show how to create your first Gridx grid in XPages.

Gridx Series

Gridx in XPages — Entire Series

Creating the Grid

Assuming you have already followed the steps in the last post, you are ready to create your first grid.

A Gridx grid requires the following:

  • A data store
  • A cache implementation
  • Defining grid columns
  • Defining the grid
  • Placing and instantiating the grid

The example is a very simple one with hard-coded data. The goal here is to get a grid up and running so we can build upon it in future posts.

It starts with a script tag and code that uses Dojo AMD loading to include the required modules. In the code below, the Gridx/Grid module is loaded and given the name Grid; a dojo Memory store module is loaded and given the name Memory, and a synchronous cache module is loaded and given the name Sync. The dojo.domReady! module is also loaded — this causes the script to wait until the page is ready before executing.

  require([
    "gridx/Grid", "dojo/store/Memory",
    "gridx/core/model/cache/Sync", "dojo/domReady!"
  ], function(Grid, Memory, Cache) {

First, we need to set up the data store. This example uses a Memory store to hold hard-coded data.

  var store = new Memory({
    data: [
      {id: 1, name: 'John', country: 'United States', things: 100},
      {id: 2, name: 'Bill', country: 'Lithuania', things: 57},
      {id: 3, name: 'Bob', country: 'China', things: 123},
      {id: 4, name: 'Jim', country: 'Germany', things: 154},
      {id: 5, name: 'Tom', country: 'Brazil', things: 78}
    ]
  });

Next, we need to define the column structure for the grid. The field attribute maps to the name attribute of the data store. The name attribute of the column defines the column title to display. When a width is not defined, it appears that it will take up the remaining available width.

		
  var columns = [
    {id: 'name', field: 'name', name: 'Name', width: '70px'},
    {id: 'country', field: 'country', name: 'Country'},
    {id: 'things', field: 'things', name: 'Things', width: '40px'}
  ];

Then, we need to define the grid object. The object takes the cache, data store, and column structure.

  grid = new Grid({ 
    id: "my_gridX", 
    cacheClass: Cache, 
    store: store, 
    structure: columns 
  });

Finally, we need to tell the grid where to be placed and then initialize it with the startup method.

		
  //Put it into the DOM tree.
  grid.placeAt('gridX_Here');
  grid.startup(); 

The placeAt call needs the ID of an element where the grid should be generated.

This div tag on the page provides a place. IMPORTANT: The div must have a height defined, or it will end up at 0 and you won’t see anything. (This example defines it inline, but it’s better to use CSS.)

<div id="gridX_Here" style="height:300px; width:300px;"></div>

CSS

The grid also requires a few stylesheets. This example uses the Claro theme. Those stylesheets can be loaded from the existing Dojo implementation on the server. The URLs use /.ibmxspres/dojoroot/, which tells the server to look in the root Dojo directory.

The third stylesheet is specific to Gridx and is loaded with a URL relative to the application’s WebContent directory.

  <xp:this.resources>
    <!-- Claro Theme -->
    <xp:styleSheet href="/.ibmxspres/dojoroot/dijit/themes/claro/claro.css">
    </xp:styleSheet>
    <xp:styleSheet href="/.ibmxspres/dojoroot/dijit/themes/claro/document.css">
    </xp:styleSheet>
		
    <!-- -Main GridX Stylesheet  -->
    <xp:styleSheet href="gridx/resources/claro/Gridx.css">
    </xp:styleSheet>
  </xp:this.resources>

Full Page Source

Putting this code together with the setup code from the last post, here is the full source of my sample page:

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

  <xp:this.resources>
    <!-- Claro Theme -->
    <xp:styleSheet href="/.ibmxspres/dojoroot/dijit/themes/claro/claro.css">
    </xp:styleSheet>
    <xp:styleSheet href="/.ibmxspres/dojoroot/dijit/themes/claro/document.css">
    </xp:styleSheet>
		
    <!-- -Main GridX Stylesheet  -->
    <xp:styleSheet href="gridx/resources/claro/Gridx.css">
    </xp:styleSheet>
  </xp:this.resources>


  <xp:this.properties>
    <xp:parameter name="xsp.client.script.dojo.djConfig"
      value="modulePaths:modPaths">
    </xp:parameter>
  </xp:this.properties>

	<xp:this.beforePageLoad><![CDATA[#{javascript:// Get the relative URL up to the .nsf
var relativeUrl = context.getUrl().getPath();
var index = relativeUrl.indexOf('.nsf');
var gridxPath = relativeUrl.substring(0, index+4) + '/gridx';
	
// Write out a JS variable with the module path for djConfig
// This works around a problem of the property being improperly escaped.
// Solution by Sven Hasselbach: http://stackoverflow.com/questions/13720849/djconfig-in-xpages-escapes-values-making-the-setting-wrong
var exCon = facesContext.getExternalContext();
var response = exCon.getResponse();
var writer = response.getWriter();
writer.write("<script>\n");
// writer.write("var modPaths = {'gridx':'/Gridx_Testing.nsf/gridx'}");
writer.write("var modPaths = {'gridx':'" + gridxPath + "'}");
writer.write("\n</script>\n");}]]>
</xp:this.beforePageLoad>

  <script>
  require([
    "gridx/Grid", "dojo/store/Memory",
    "gridx/core/model/cache/Sync", "dojo/domReady!"
  ], function(Grid, Memory, Cache) {

  var store = new Memory({
    data: [
      {id: 1, name: 'John', country: 'United States', things: 100},
      {id: 2, name: 'Bill', country: 'Lithuania', things: 57},
      {id: 3, name: 'Bob', country: 'China', things: 123},
      {id: 4, name: 'Jim', country: 'Germany', things: 154},
      {id: 5, name: 'Tom', country: 'Brazil', things: 78}
    ]
  });
		
  var columns = [
    {id: 'name', field: 'name', name: 'Name', width: '70px'},
    {id: 'country', field: 'country', name: 'Country'},
    {id: 'things', field: 'things', name: 'Things', width: '40px'}
  ];

  grid = new Grid({ 
    id: "my_gridX", 
    cacheClass: Cache, 
    store: store, 
    structure: columns 
  }); 
		
  //Put it into the DOM tree.
  grid.placeAt('gridX_Here');
  grid.startup(); 

});
</script>

<div id="gridX_Here" style="height:300px; width:300px;">
</div>

</xp:view>

If you’ve include gridx in your WebContent folder as shown in the last post, you should be able to copy this source into an XPage and load a grid.

Gridx 3 - Grid

It doesn’t do anything yet, but we’ll add features soon.

Gridx in XPages – Using djConfig to Make the Code Available

The last post covered some of the benefits of using Gridx, but before we can create a Gridx grid in XPages, we have to make the code available to the application. In this post, I’ll show how to use djConfig to make the code available to the NSF. This is not only the key to allowing Gridx to work, but the concept will allow other Dojo libraries to be included as well.

Update – A Simpler Method

The code below works as shown, but a simpler method for making the Gridx code available to the application is described here.

Gridx Series

Gridx in XPages — Entire Series

Dojo Version Required

Gridx uses AMD-style module loading, so it seems to need Dojo 1.7 or higher. The documentation says that it works best with 1.8.0 or higher. Notes 9 ships with Dojo 1.8.1, so that is what I will be using for my testing and development.

Installation

Including Gridx in an application is different than including a module (as with the first step in using Dojo EnhancedGrid) because the modules for EnhancedGrid are already part of the standard Dojo installation, so the files are already on the server and just need to be included on the page to be available.

As with third-party JavaScript libraries, you can easily add the code to an NSF.

First, download gridx and extract it.

Rename the folder above core, modules, etc to just gridx (without the version number).
Gridx 2 - Directory Structure

Then, open your NSF if the Package Explorer view and drag the gridx folder into WebContent
Gridx 2 - WebContent Folder

Making the Code Available

The next step is making the code available to use within the application.

Dojo would generally look for the code in a sibling directory to dojo, so it would expect to find it in this directory structure:

  • dojo
  • dijit
  • dojox
  • gridx

This is the point where I was stuck the last time I tried this.

You can set up your own dojo installation on yoru server and reference it. While it works, it creates a bigger administrative burden to maintain it and update it and it leaves you operating with a different version than was intended with your current version of XPages. You could also go the route of OSGi plugin, but, at this point, I want to add the library to the application and use it like I do with any other JavaScript library.

djConfig modulePaths

The key to making it work like any other library lies in djConfig — the Dojo configuration object. There’s an attribute named modulePaths that can include a list of additional places (outside of the standard directory structure) to look for code.

Once this is configured, we can reference the Gridx code as thought it exists in the default Dojo structure.

Here’s an example from the Dojo documentation:

modulePaths: {'foo': '../../bar'}

Setting modulePaths in XPages

In XPages, you can set a djConfig property as an XPage or Custom Control property. Select the xp:view tag and go to the Properties view. Select All Properties > Data > Properties and click the + button to add a property.

The property name is xsp.client.script.dojo.djConfig

The property value should be something like this: modulePaths: {'gridx': '/myDB.nsf/gridx'}

But it doesn’t work. You get a "SyntaxError: invalid property id" error in the browser console.

Gridx 2 - ModulePaths Error

The issue is that the value is escaped incorrectly by XPages, so it doesn’t allow it to work.

Fortunately, Sven Hasselbach posted a workaroud for this type of problem. You can use the beforePageLoad event to write out a client-side JavaScript variable with the modulePath setting (like an XAgent) so that it can be referenced by the djConfig property.

// Get the relative URL up to the .nsf
var relativeUrl = context.getUrl().getPath();
var index = relativeUrl.indexOf('.nsf');
var gridxPath = relativeUrl.substring(0, index+4) + '/gridx';

// Write out a JS variable with the module path for djConfig
// This works around a problem of the property being improperly escaped.
// Solution by Sven Hasselbach: http://stackoverflow.com/questions/13720849/djconfig-in-xpages-escapes-values-making-the-setting-wrong
var exCon = facesContext.getExternalContext();
var response = exCon.getResponse();
var writer = response.getWriter();
writer.write("<script>\n");
writer.write("var modPaths = {'gridx':'" + gridxPath + "'}");
writer.write("\n</script>\n");

The lines 2-4 build a relative URL up to the .nsf. It then appends /gridx, because that’s where the directory is accessible starting with the WebContent folder.

Lines 9-14 write out a script tag with a client-side JavaScript variable named modPaths.

Now we can reference that modPaths variable in our djConfig property, because the code above runs before the page is loaded:

<xp:this.properties>
  <xp:parameter name="xsp.client.script.dojo.djConfig"
    value="modulePaths:modPaths">
  </xp:parameter>
</xp:this.properties>

Here’s what the property (and related source code) looks like:

Gridx 2 - djConfig Property

Up Next

Now, the gridx code is available to our application. In the next post, I’ll show how to get a simple gridx grid loading.