Create a Categorized Dojo TreeGrid in XPages

Neither the Dojo DataGrid nor the Dojo EnhancedGrid provide the ability to categorize data, but there is another grid module called the TreeGrid that you can use if you need a categorized grid. In this short series, we’ll take a look at how to create a TreeGrid and customize it.

Categorized Grid

Here’s a screen shot of what the data from the FakeNames database looks like when categorized by State:

TreeGrid_1_a

Programmatic Declaration

For this type of grid, we’ll be declaring it programmatically and not with the Dojo Data Grid control from the Extension Library / 8.5.3 UP1 / Notes9. Using a technique similar to this post it is possible to instruct a Dojo Data Grid control to render as a TreeGrid, but the data provided to the grid must be in a significantly different format than a DataGrid or EnhancedGrid, so I took a different approach to create this one.

Steps

At a high level, the steps to create the categorized grid are as follows:

  1. Include the required dojo modules and style sheets
  2. Set the XPage to parse dojo on load
  3. Define a div to render the grid
  4. Execute code onClientLoad to create the grid
  5. Provide the data for the grid

1. Include the required dojo modules and style sheets

Along with the TreeGrid module, two additional modules are required for the grid’s data store. The ItemFileWriteStore is a standard data source object, but the ForestStoreModel is also required in order to format the data properly for the TreeGrid.

In addition, you’ll need to include several dojo stylesheets. The Dojo Data Grid control loads some of these on its own, but we’ll need to include them manually because we’re not using the control this time.

The resources of the page should look like this:

<xp:this.resources>
  <xp:dojoModule name="dojox.grid.TreeGrid"></xp:dojoModule>
  <xp:dojoModule name="dijit.tree.ForestStoreModel"></xp:dojoModule>
  <xp:dojoModule name="dojo.data.ItemFileWriteStore"></xp:dojoModule>

  <xp:styleSheet
    href="/.ibmxspres/dojoroot/dojox/grid/resources/Grid.css">
  </xp:styleSheet>
  <xp:styleSheet
    href="/.ibmxspres/dojoroot/dijit/themes/tundra/tundra.css">
  </xp:styleSheet>
  <xp:styleSheet
    href="/.ibmxspres/dojoroot/dojox/grid/resources/tundraGrid.css">
  </xp:styleSheet>		
</xp:this.resources>

2. Set the XPage to parse dojo on load

The XPage’s Trigger Dojo parse on load property must be selected, or the grid will not be rendered. (This is another thing that you don’t have to set manually when using the grid control.)

TreeGrid_1_b

3. Define a div to render the grid

Since we’re not using the control to automatically define the place to render the grid, we just need to add a div tag to the page and give it an ID so we can reference it to draw the grid.

<div id="treeGrid"></div>

4. Execute code onClientLoad to create the grid

The code below actually defines and creates the grid.

Lines 1-5 define the grid layout. With the Dojo Data Grid control, the layout columns were defined by Dojo Data Grid Column controls. Since we’re generating the grid ourselves, we need to define
the layout. The category column should be listed as the first column. If it’s not included, then the expand/collapse icon will be displayed next to an ellipsis (…) without any other information that defines the category. The layout is pretty straightforward. The ‘name’ property defines the column title and the ‘field’ property defines the column name.

Line 7 sets up the data store for the grid. It retrieves the data from another XPage in the same database that provides the data in the required format. (More on that below.)

Lines 9-15 define the ForestStoreModel for the grid. This is the data model required for the categorization. It includes the data store defined above. The childrenAttrs property defines the attribute that specifies the children for each category. The query property is required to select the category items. Without this property, each set of child items is listed twice under each category, but only one set of the child items will actually collapse.

Lines 17-22 actually create the grid and define the data model and the grid layout that were set up earlier in the code. The last parameter in line 20 is the ID of the div where the grid will be rendered.

var layout = [
  { name: "State", field: "state"},
  { name: "First Name", field: "firstname"},
  { name: "Last Name", field: "lastname"}
];
			
var jsonStore = new dojo.data.ItemFileWriteStore({ url: "TreeGrid_DataStore.xsp"});

var treeModel = new dijit.tree.ForestStoreModel({
  store: jsonStore,
  query: {type: 'state'},
  rootId: 'personRoot',
  rootLabel: 'People',
  childrenAttrs: ['children']
});

var grid = new dojox.grid.TreeGrid({
  treeModel: treeModel,
  structure: layout
}, 'treeGrid');

grid.startup();

dojo.connect(window, "onresize", grid, "resize");

5. Provide the data for the grid

This is much more involved than in previous grids, because the data must be in a customized format that the built-in REST services do not provide.

This sample data, taken from the dojo documentation, shows the hierarchy. This shows the code for a category that has two children displaying underneath it.

...
{ id: 'AS', name:'Asia', type:'continent',
children:[{_reference:'CN'}, {_reference:'IN'}] },
{ id: 'CN', name:'China', type:'country' },
{ id: 'IN', name:'India', type:'country' },
...

Each category item must have a children item, containing a list of references to the child elements by their ID.

In order to provide this data, I wrote code to walk through a categorized view and write out the required JSON as an XAgent. The code in the section above references the XAgent page in order to read the data.

Check out Stephan Wissel’s post if you’re unfamiliar with the concept of an XAgent

// Read view data and write out the JSON data for the categorized grid
// Each view entry is written out at the top level, but each category has a children property that is an array of IDs of child entries to indent.
// There can be any number of categorization levels -- just add 'children' properties to child entries.
// NOTE: It needs the newlines after each line between the write() statements or the browser doesn't see the output as JSON

var externalContext = facesContext.getExternalContext();
var writer = facesContext.getResponseWriter();
var response = externalContext.getResponse();

response.setContentType('application/json');
response.setHeader('Cache-Control', 'no-cache');

writer.write("{\n");
writer.write("identifier: 'id',\n");
writer.write("label: 'name', \n");
writer.write("items: [\n");

var categoryItem = "";
var childItems = "";

// Walk the view and build the JSON data
var vw:NotesView = database.getView('ByState');
var nav:NotesViewNavigator = vw.createViewNav();
var ve:NotesViewEntry = nav.getFirst();

while (ve != null) {
	var cv = ve.getColumnValues();

	// When a categorized entry is reached:
	// (a) write out the previous category and children
	// (b) set up the new category element	
	if (ve.isCategory()) {
		// Write out the previous category and child entries		
		if (categoryItem != "") {
			// Trim the trailing comma and space off the category item. 
			// (The child items still need to end with a comma before the next category item starts.)
			categoryItem = categoryItem.substring(0, categoryItem.length - 2);
			writer.write("\n" + categoryItem + "] }, \n" + childItems);
		}	
	
		// Start building the new category and child entries
		categoryItem = "  {id:'" + cv[0] + "', type: 'state', state:'" + cv[0] + "', children:[";
		childItems = "";
	
	} else {
		// This isn't a category, so simultaneously build the children property and the child entries, until the next category is reached.
		categoryItem += "{_reference:'" + ve.getUniversalID() + "'}, ";
childItems += "{id:'" + ve.getUniversalID() + "', firstname:'" + cv[1] + "', lastname: '" + cv[2] + "'}, "
	
	}	
	
	// Get the next entry and recycle the current one
	var tmpentry = nav.getNext();
	ve.recycle();
	ve = tmpentry;
}


// Write out the last category and children, without the trailing commas
categoryItem = categoryItem.substring(0, categoryItem.length - 2);
childItems = childItems.substring(0, childItems.length - 2);
writer.write("\n" + categoryItem + "] }, \n" + childItems);

// Close the JSON string
writer.write("]}");
writer.endDocument();

Strangely, the newline (\n) characters were required when writing out the data. Otherwise, the response was not interpreted as JSON — the browser would return nothing.

In line 42, you can see that I’m adding a property type: ‘state’ to each category element. I don’t need to display this value, but, as I mentioned above, it is required for the ForestStoreModel’s query parameter in order to properly categorize the documents without duplicating entries.

Important Performance Note

Because I am writing out the JSON for the entire view, this will be loading all items from the view up front, so there’s overhead in this method. Fortunately, JSON data is compressed with gzip. In this case, 1,301 records was 27k. If you need to work with larger data sets, then you may need to consider either rolling your own REST service or passing a parameter to the XAgent page to search and limit the amount of data that is generated.

Up Next

In the next post, I’ll review some of the properties available to the TreeGrid.

Advertisements

22 responses to “Create a Categorized Dojo TreeGrid in XPages”

  1. Bill Fox says :

    I’m selecting a group of documents via a FTI search and have a DocumentCollection, Unfortunately the FTI destroys the sort order. I have a piece of code that will sort the document collection and return it in the right sort order. So I’m assuming that I could then walk through the returned DocumentCollection and create the JSON stream similar to your walk through the viewEntries. This will obviously put a pretty fair front-end load on the page, but the returned collection will seldom be more than say 25 – 30 documents so it should not be a significant issue. It will just be a selection of active documents for a given user, however, the query is rather complex.

    • Brad Balassaitis says :

      That makes sense, given that FT searches always seem to order the documents by search relevance, rather than by any predefined sort order. Fortunately, a sort on a small amount of documents wouldn’t require too much processing overhead.

  2. Kjell Dirdal says :

    Hi, I’m struggling a bit to get TreeGrid to work. Have followed the steps all the way. Do not get up any data in XPages. Does anyone have a sample database that shows the steps.

    • Brad Balassaitis says :

      First thing to try is to make sure you have data available. Can you open your XAgent page that writes out the data and verify that it looks correct (and is properly formatted)?

      • Kjell Dirdal says :

        It seems that the xAgent is not running

      • Kjell Dirdal says :

        I have fixed the xAgent. The structure seems to be correct. Try to run the test page for Treeview, but get there was an error. I can see the heading of the grid but noting more. How do I debug i dojo/xpage.

      • Brad Balassaitis says :

        I don’t know of a good way to debug the grid. That’s why I always try to check the data separately. Make sure that the data is formatted properly. If the data looks good, then it’s likely that the problem is within the structure of the grid. Try trimming as much as you can out of the grid creation code so that it just has one column and no extra features, aside from what’s required to get it loaded. Make sure the name of the column matches the name of a property that is being written out. It’s case-sensitive, so keep an eye on that, too.

      • Brad Balassaitis says :

        I saw a response to this on Stack Overflow about the XAgent rendered flag. Did that fix the problem?

      • Kjell Dirdal says :

        Fantastic, I had forgotten to set rendered to false in XAgent. It works now. Do you know how to remove the three dots. in the cells with no value?

      • Brad Balassaitis says :

        Great — glad to hear it! As for the dots in the column, I’ve only seen them when I messed up the data source and there wasn’t anything to display. However, if they’re there for a legitimate reason, I would think you could take care of them with a column formatter function. You can use that (it’s client-side JavaScript) to return anything you want to display in the column and it can be conditional based on the value that’s supposed to be there.

        This post talks about how to use formatter functions: https://xcellerant.net/dojo-data-grid-20-icon-columns/

      • Kjell Dirdal says :

        How do I get the values from the selected row into an

      • Kjell Dirdal says :

        How do I get the values from the selected row into an xp:inputText?

      • Brad Balassaitis says :

        There’s code in this post that shows how to get the selected row index and one attribute from the selected row: https://xcellerant.net/2013/04/10/dojo-data-grid-8-opening-documents/

        You could adapt that code to read more column values from the row with client-side javascript and write that information into an edit box by referencing it like this: dojo.byId(‘#{id:FIELD_ID}’).value = NEW_VALUE

      • Kjell Dirdal says :

        I have one question regarding Tree grid. I have tried to add a third column in tree grid. and datasource.

        The items in each category is like the line below
        childItems += “{id:'” + ve.getUniversalID() + “‘, vrakkode:'” + cv[1] + “‘, vrak_aarsak_kode:'” + cv[2] + “‘, vrak_aarsak_kode:'” + cv[3] + “‘}, ”

        Here is the code in the tree grid.
        { name: “Vrak gruppe”, field: “vrakgruppe”, ‘width’: ‘200px’},
        { name: “Vrak kode”, field: “vrakkode”, ‘width’: ‘100px’},
        { name: “Vrak årsak kode”, field: “vrak_aarsak_kode”, ‘width’: ‘200px’},
        { name: “Description”, field: “vrak_aarsak_kode”, ‘width’: ‘200px’}

        Some how, i get error when running with three columns under each category.
        When I run with two columns it works normal.

        Do you know why?

      • Brad Balassaitis says :

        I think the issue is that the “width” attribute has quotes around it in the code above, but it should not. The value (‘200px’) should have quotes around it, but not the attribute name (width)

  3. olli k says :

    I had to move onClientLoad -script to Output script because onClientLoad event didn’t run for some reason(Notes Client 8.5.3).. Thanks for a great example!

  4. Gael Duret says :

    Hello, thank you very much for this useful sample… I’m trying to setup it, but I have a strange error that I cannot explain :
    I always get an error on line : “dojo.data.ItemFileWriteStore”, because “dojo.data” is undefined ?

    I’m using a domino server 8.5.2 FP4, with dojo version 1.4.3 ?
    dojo.data seem to be in toolkit since version 1 ?

    Any idea are welcome.

    • Brad Balassaitis says :

      I see the same thing as you in the Dojo documentation — dojo.data.ItemFileWriteStore has been around since v1.

      Did you add the dojo module resource to make that module available to the page?

  5. Simon says :

    Is it posible to use the dojo grid tree with parent and responce document if jyes how below when i try the below code its not displaying properly:

    while (ve != null) {
    var cv = ve.getColumnValues();
    if (ve.getDocument().isResponse()) {
    parentItem += “{_reference:'” + ve.getUniversalID() + “‘}, “;
    childItems += “{id:'” + ve.getUniversalID() + “‘, FolderTitle:'” + cv[1] + “‘}, “// “‘, FolderAllow: ‘” + cv[2] + “‘}, ”
    } else {
    if (parentItem != “”) {
    categoryItem = parentItem.substring(0, parentItem.length – 2);
    writer.write(“\n” + parentItem + “] }, \n” + childItems);
    }
    parentItem = ” {id:'” + cv[0] + “‘, type: ‘FolderTitle’, FolderTitle:'” + cv[0] + “‘, children:[“;
    childItems = “”;
    }
    var tmpentry = nav.getNext();
    ve.recycle();
    ve = tmpentry;
    }

    parentItem = parentItem.substring(0, parentItem.length – 2);
    childItems = childItems.substring(0, childItems.length – 2);
    writer.write(“\n” + parentItem + “] }, \n” + childItems);
    writer.write(“]}”);
    writer.endDocument();

  6. Mark says :

    Hi, Is there a way to remove the three dots? I’m using the tree grid. I also checked the column formatter function, how to use it in tree grid? Can you direct me to any tutorial for that? thanks

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: