A Dynamic Dojo Data Grid component

The other day I blogged about First Steps in learning Java and used a Managed Bean to get the values from some fields and create some HTML from those values. Well today we’ll take another step forward by creating our own custom component (obviously I’m a firm believer of diving in head first). We’ll be constructing a component that will build a dynamic dojo data grid from a view in the current database. I created this because after creating about 3 different views using a data grid I came to the conclusion that all that work sucked and decided to make life a little bit easier on myself. It does have limited functionality mainly because I want this to be something that is easy to use when creating a lot of views, for example the excludeColumns property of this component is a comma separated list of view columns you don’t want to include in the data grid. If you want to do more with the data grid then I recommend just building the data grid the old fashioned way with the columns and features you require.

Let’s get started. First we’ll need to create the java class that will be used to create and render this component. This class will contain our properties as private variables and then public getters and setters for each property. It will extend the UIComponentBase component. When a java class extends another java class it inherits all of the properties and methods of the class that was extended. Any methods you override, if needed you can call super.methodName() to run the overridden method from the class we extended. Which if you override encodeBegin, encodeChildren or encodeEnd it’s generally a good idea to call the super.methodName to ensure nothing is missed that may be required. We added the following properties:

  • viewName – The name of the view that we’re going to display in the data grid. This is a required property.
  • exludeColumns – This is a comma separated list of columns we don’t want to show up in the grid.
  • editableColumns – This is a comma separated list of columns that we want to be editable from the data grid
  • doubleClickOpensDoc – If true, when you double-click a row it will open the document
  • xpageName – The name of the XPage to use to open the document with

Here’s our component class:

package com.keithstric.components;

import java.io.IOException;
import java.util.Vector;

import javax.faces.component.UIComponentBase;
import javax.faces.context.FacesContext;
import javax.faces.el.ValueBinding;

import lotus.domino.Database;
import lotus.domino.NotesException;
import lotus.domino.View;
import lotus.domino.ViewColumn;

import com.ibm.domino.xsp.module.nsf.NotesContext;
import com.ibm.xsp.component.UIScriptCollector;
import com.ibm.xsp.extlib.component.dojo.grid.UIDojoDataGrid;
import com.ibm.xsp.extlib.component.dojo.grid.UIDojoDataGridColumn;
import com.ibm.xsp.extlib.component.rest.DominoViewItemFileService;
import com.ibm.xsp.extlib.component.rest.UIRestService;

public class DynamicDataGrid extends UIComponentBase {

	private static String FAMILY = "com.keithstric";
	private static String COMPONENT_TYPE = "com.keithstric.DynamicDataGrid";	
	private String viewName;
	private String excludeColumns;
	private String editableColumns;
	private boolean doubleClickOpensDoc = false;
	private String xpageName;
	
	public DynamicDataGrid() {}

	@SuppressWarnings("unchecked")
	@Override
	public void encodeBegin(FacesContext context) throws IOException {
		try {
			//Create a data grid component
			UIDojoDataGrid dataGrid = new UIDojoDataGrid();
			dataGrid.setStyle("height: 25em;");
			dataGrid.setStoreComponentId("ddgRestService");
			if (doubleClickOpensDoc) {
				//This is actually a client side script, hence the location.href call
				dataGrid.setOnDblClick("var dataGridDij = dijit.byId(dojo.query(\"[id$='dynamicDataGrid']\")[0].id);" +
					"location.href = '" + xpageName + ".xsp?documentId=' + dataGridDij.selection.getSelected()[0].attributes['@unid'] + '&action=editDocument';"
				);
			}
			dataGrid.setId("dynamicDataGrid");
			String editableColumnsList = getEditableColumns().toLowerCase();
			String excludeColumnsList = getExcludeColumns().toLowerCase();
			Vector<ViewColumn> viewColumns = getViewColumns();
			if (!viewColumns.isEmpty()) {
				//loop through each of the view's columns and add a dojo data grid column if it's not excluded
				for (ViewColumn kid : viewColumns) {
					if (excludeColumnsList.indexOf(kid.getTitle().toLowerCase()) == -1) {
						UIDojoDataGridColumn column;
						boolean editable = false;
						if (editableColumnsList.indexOf(kid.getTitle().toLowerCase()) > -1) {
							editable = true;
						}
						column = createColumn(kid.getItemName(), kid.getTitle(), editable);
						//Add this dojo data grid column component to the data grid
						dataGrid.getChildren().add(column);
					}
				}
				//Create the rest service
				UIRestService restService = createRestService(viewName);
				//Add the rest service and data grid to this components parent component
				this.getParent().getChildren().add(restService);
				this.getParent().getChildren().add(dataGrid);
			}
		} catch (NotesException e) {
			e.printStackTrace();
		}
		super.encodeBegin(context);
	}
	
	@Override
	public void encodeEnd(FacesContext context) throws IOException {
		super.encodeEnd(context);
		//Now refresh this component's parent so that it gets rendered
		UIScriptCollector.find().addScript("XSP.addOnLoad(function(){" +
				"XSP.partialRefreshGet('" + this.getParent().getClientId(context) + "');" +
			"});"
		);
	}
	
	@SuppressWarnings("unused")
	private UIDojoDataGridColumn createColumn(String fieldName, String label, boolean isEditable) {
		UIDojoDataGridColumn ddgColumn = new UIDojoDataGridColumn();
		ddgColumn.setEditable(isEditable);
		ddgColumn.setField(fieldName);
		ddgColumn.setLabel(label);
		ddgColumn.setWidth("auto");
		return ddgColumn;
	}
	
	@SuppressWarnings({ "unchecked", "unused" })
	private Vector<ViewColumn> getViewColumns() {
		try {
			Database db = NotesContext.getCurrent().getCurrentDatabase();
			View curView = db.getView(viewName);
			Vector<ViewColumn> columns = curView.getColumns();
			return columns;
		} catch (NotesException e) {
			e.printStackTrace();
		}
		return null;
	}
	
	private UIRestService createRestService(String viewName) {
		DominoViewItemFileService restView = new DominoViewItemFileService();
		restView.setViewName(viewName);
		restView.setVar("entryRow");
		restView.setContentType("application/json");
		restView.setDefaultColumns(true);

		UIRestService restService = new UIRestService();
		restService.setId("ddgRestService");
		restService.setService(restView);
		return restService;
	}
	
	public String getViewName() {
		if (null != viewName) {
			return viewName;
		}
		ValueBinding vb = getValueBinding("viewName");
		if (vb != null) {
			return (String) vb.getValue(getFacesContext());
		} else {
			return null;
		}
	}

	public void setViewName(String viewName) {
		this.viewName = viewName;
	}

	public String getExcludeColumns() {
		if (null != excludeColumns) {
			return excludeColumns;
		}
		ValueBinding vb = getValueBinding("excludeColumns");
		if (vb != null) {
			return (String) vb.getValue(getFacesContext());
		} else {
			return "";
		}
	}

	public void setExcludeColumns(String excludeColumns) {
		this.excludeColumns = excludeColumns;
	}

	public String getEditableColumns() {
		if (null != editableColumns) {
			return editableColumns;
		}
		ValueBinding vb = getValueBinding("editableColumns");
		if (vb != null) {
			return (String) vb.getValue(getFacesContext());
		} else {
			return "";
		}
	}

	public void setEditableColumns(String editableColumns) {
		this.editableColumns = editableColumns;
	}

	@Override
	public String getFamily() {
		return FAMILY;
	}

	public boolean isDoubleClickOpensDoc() {
		return doubleClickOpensDoc;
	}

	public void setDoubleClickOpensDoc(boolean doubleClickOpensDoc) {
		this.doubleClickOpensDoc = doubleClickOpensDoc;
	}

	public String getXpageName() {
		if (null != xpageName) {
			return xpageName;
		}
		ValueBinding vb = getValueBinding("xpageName");
		if (vb != null) {
			return (String) vb.getValue(getFacesContext());
		} else {
			return null;
		}
	}

	public void setXpageName(String xpageName) {
		this.xpageName = xpageName;
	}
}

All in all not anything that extraordinary here, I’ll get to the encodeBegin and encodeEnd methods here shortly.

Now to make this component available in the component palette of DDE we’ll need to add the component, along with all the component’s properties to a new xsp-config file. Open your package explorer and navigate to the “Web Content\WEB-INF” folder and create a new file. The filename isn’t important but must end in .xsp-config. This file will make all the properties for the component available in DDE with the appropriate editor for the property’s listed data type. Here is our xsp-config file:

<?xml version="1.0" encoding="UTF-8"?>
<faces-config>
	<faces-config-extension>
		<namespace-uri>http://www.ibm.com/xsp/custom</namespace-uri>
		<default-prefix>xc</default-prefix>
	</faces-config-extension>
	<component id="DynamicDataGrid">
		<description>A dynamic dojo data grid. This currently doesn't work with Categorized views. To access row data use the variable "entryRow"</description>
		<display-name>Dynamic Dojo Data Grid</display-name>
		<component-type>com.keithstric.DynamicDataGrid</component-type>
		<component-class>com.keithstric.components.DynamicDataGrid</component-class>
		<component-extension>
			<component-family>com.keithstric</component-family>
			<tag-name>dynamicDataGrid</tag-name>
			<designer-extension>
				<in-palette>true</in-palette>
				<category>keithstric</category>
			</designer-extension>
		</component-extension>
		<property id="viewName">
      		<description>Name of the Domino view to create the data grid from</description>
      		<display-name>View Name</display-name>
      		<property-name>viewName</property-name>
      		<property-class>java.lang.String</property-class>
      		<property-extension>
				<required>true</required>
			</property-extension>
		</property>
		<property id="editableColumns">
			<description>A comma separated list of column titles that will allow editing (i.e. column 1,column 2)</description>
			<display-name>Editable Column List</display-name>
			<property-name>editableColumns</property-name>
			<property-class>java.lang.String</property-class>
		</property>
		<property id="excludeColumns">
			<description>A comma separated list of column titles that will be excluded from the grid (i.e. column 1,column 2)</description>
			<display-name>Excluded Columns List</display-name>
			<property-name>excludeColumns</property-name>
			<property-class>java.lang.String</property-class>
		</property>
		<property id="xpageName">
			<description>The name of the XPage that is associated with the documents in this view (do not include .xsp at the end and it is case sensitive)</description>
			<display-name>XPage Name</display-name>
			<property-name>xpageName</property-name>
			<property-class>java.lang.String</property-class>
			<property-extension>
				<required>true</required>
			</property-extension>
		</property>
		<property id="doubleClickOpensDoc">
			<description>Flag to indicate that double clicking a row opens the document</description>
			<display-name>Double Click Opens Doc</display-name>
			<property-name>doubleClickOpensDoc</property-name>
			<property-class>boolean</property-class>
		</property>
	</component>
</faces-config>

Now in the DDE component palette you should see the new custom component. If you don’t see it here, ensure the properties listed in the xsp-config file are properly spelled and that no typo errors are anywhere in this file. If you don’t see it in the palette on the right OR in the “Other” component dialog something is wrong in xsp-config somewhere.
Here is the “Other” component dialog selection:
Controls - IBM Lotus Domino Designer_2011-05-04_14-13-49.png

And here is the component in the dialog
Controls - IBM Lotus Domino Designer_2011-05-04_14-13-27.png

Our properties should also show up when the component is added to a page and that component selected:
test - Custom Control - IBM Lotus Domino Designer_2011-05-04_14-18-18.png

Since we’ve got all this in place let’s talk about the encodeBegin and encodeEnd methods of our component. The encode* methods are automatically called by the JSF engine before a component is rendered. This allows us to manipulate it’s contents at runtime. So, to start with in encodeBegin, we create a new UIDojoDataGrid (which is part of the extension library) and set it’s initial properties. Notice I set a default height, this is so that if the view is empty you at least get the column headers and can see that the view is empty. We set the StoreComponentId to the id of the rest service we’ll create later in this same method. We also set the onDblClick event script so that a document can be opened when a row is double clicked if the component property of . Next we get the view that’s listed in the viewName property of the component and get all of it’s columns. We then loop through each colum and add a UIDojoDataGridColumn with the proper properties. Notice to add these columns we’re using “dataGrid.getChildren().add(UIDojoDataGridColumn), this adds this column to the component tree as a child of the Data Grid.

Next we create the rest service with all the proper properties (mainly the ID, variable name, view name and content type and service) and assign it an ID so that we always know what the ID is. This is pertinent since this ID is what is stored in the Data Grid’s storeComponentId property. The rest service is what provides the data for the data grid. Once we’ve constructed the rest service, we then add the rest service and the data grid as a child of the Dynamic Data Grid’s parent using “this.getParent().getChildren().add(component)”. Lastly in the encodeEnd method, we put an “addOnLoad” script onto the page to execute a partial refresh of the Dynamic Data Grid’s parent so that it appears on the page. Something of note here also is that this component must be within a panel or <xp:div&#62 or some other component. If not you’ll have some problems. I guess we could remedy this by creating a panel or xp:div component in the encodeBegin to move this component to, I’ll have to play with that and see what I can come up with.

One peculiar thing about this component is that it doesn’t have it’s own renderer. This is because we’re really not creating a custom component that has it’s own structure or visual representation on the page. This component creates other components that have their own renderers and we don’t want to change their behaviour. We’re just using the properties and methods of this component to set the properties of the components we’re inserting.

So there you have it, a custom component that’s fairly simple and does something that will make our lives much easier. You can see a demo of this component here. This demo contains 2 pages of the same view with different properties, which are listed below the view so you can see the differences. Enjoy!

Share This: