Quantcast
Channel: XPages – Xcellerant
Viewing all articles
Browse latest Browse all 216

Create a Categorized Dojo TreeGrid in XPages

$
0
0

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.



Viewing all articles
Browse latest Browse all 216

Trending Articles