Archive | April 2013

Dojo Data Grid – Part 13: Create a Dojo EnhancedGrid

By default the Dojo Data Grid control generates a standard dojo data grid (dojox.grid.DataGrid). But the enhanced grid (dojox.grid.EnhancedGrid) has some cool plugins that can further enhance the functionality. In this post, I’ll show how to get the Dojo Data Grid control to generate an enhanced grid.

Dojo Data Grid Series

Default – DataGrid

As you can see in the screen shot below, the Dojo Data Grid control creates a dojox.grid.DataGrid:

Dojo_13_1_DefaultGridType

EnhancedGrid

In order to take advantage of the enhanced features, we need to get the control to generate an EnhancedGrid instead.

Fortunately, we can do this pretty easily.

1. Include the EnhancedGrid module

Include the dojox.grid.EnhancedGrid module on the page (Resources > Add > Dojo Module…)
Dojo_13_3_EnhancedGrid_Resource

2. Set the dojo type

Set the dojo type of the grid control to dojox.grid.EnhancedGrid (Properties > Dojo > Dojo type)
Dojo_13_3_DojoType

3. Load additional dojo style sheets

Additional style sheets are required to style the enhanced grid.

Without them, it looks like this:

Dojo_13_4_EnhancedGrid_NoStyleSheets

Add these definitions to the this.resources tag on the page:

<xp:styleSheet
  href="/.ibmxspres/dojoroot/dojox/grid/resources/Grid.css">
</xp:styleSheet>
<xp:styleSheet
  href="/.ibmxspres/dojoroot/dojox/grid/enhanced/resources/claroEnhancedGrid.css">
</xp:styleSheet>

Now, it looks like this:
Dojo_13_5_EnhancedGrid_WithStyleSheets

Verifying the Grid Type

Now, when you load the page and check it in Firebug, you can see that the grid is an EnhancedGrid.

Dojo_13_7_EnhancedGridType

Up Next

So far, this did not do much other than change the UI a little, but it opened the door for us to start loading plugins for enhanced functionality. In the next few posts, we’ll take a look at some of the EnhancedGrid plugins.

Tune in next time to see the awesome filtering capabilities that are provided by the EnhancedGrid!

Dojo Data Grid – Part 12: Highlighting Edited Rows

If you’ve implemented editable columns in your grid, it would be helpful to let users know which rows have been edited but not yet saved. In this post, I’ll show you how.

Dojo Data Grid Series

Row Change Event

In a previous post, I talked about the onStyleRow event that’s available to the grid. (Refer to that post for instructions on the proper way to add the event code.)

By inspecting the properties of the arguments automatically made available to that event, I found that the node has an onchange event handler available, so we can use that to track changes to the data.

Unfortunately, it is not cell-specific — as far as I can tell, you can only get a handle to the entire row.

Quick and Dirty Solution

The simplest way to use this logic is to add a function to the onchange event of the row node, from code within the onStyleRow event.

var row = arguments[0];
row.node.onchange = function () {
  row.node.style.backgroundColor = ' background-color:yellow;';
}

Now, when you edit a field in the grid, it will automatically highlight the row in yellow.

DataGrid_12_1

There are two problems with this method:

  1. It does not persist. Once you scroll far enough down in the grid, the style of that row is lost and will not be maintained when you scroll back, because the row is re-drawn and does not remember the style you added.
  2. It does not get cleared when you save or revert the changes in the grid. In my opinion, it makes the most sense to highlight a changed row and show that it has not yet been saved.

A Better (Persistent) Solution

To better keep track of the changes in the grid, you can use a global javascript variable on the page to keep track of an array of updated rows and refer to that array when styling the rows in the grid.

First, add an output script control to the page and set it’s code to this:

var editedRows = [];

Then, update the onStyleRow event code to (a) keep track of the row index when updated and (b) check whether the current row has been updated.

var row = arguments[0];

// If this row has been changed, highlight it
if (dojo.indexOf(editedRows, row.index) > -1) {
  row.customStyles += ' background-color:yellow;';
}

// Keep track of the changed row
row.node.onchange = function () {
  editedRows.push(row.index);
}

Since the list of updated rows is now in a global variable, it will persist, regardless of how much the user scrolls. (It will not persist if the entire page is refreshed, but neither would any unsaved changes.)

Lastly, update the Save and Revert buttons to clear that array and re-draw the grid, in order to force the highlighting to be removed once the changes are saved or reverted (because there’s nothing left to highlight at that point.)

This is what the Save button code now looks like:

// Reset the list of edited rows to clear the styles
editedRows = [];
var args = {onError: function() {alert('error!');}};
restViewItemFileService.save(args);

//Refresh the grid
restViewItemFileService.close();
dijit.byId('#{id:djxDataGrid1}')._refresh();

Much better!

Dojo Data Grid – Part 11: Editing Data in the Grid

A great feature of the Dojo Data Grid control that we haven’t explored yet is editable columns. They’re quick to set up and they provide an easy way for users to update data without having to open forms, edit, save, and return to the grid.

Dojo Data Grid Series

Enabling Column Editing

Allowing a column to be editable in the grid is as simple as setting the column’s editable property to true!

Grid11_1

Now, you can double-click on a cell in that column and it will change to edit mode.

Grid11_2

But there is one more step…

Saving the Changes

Updates made to the grid are not saved to the back-end documents by default. The second step is to use client-side JavaScript to save the changes to the rest service:

restServiceID.save();

The ID to use is the jsId property of the REST service, if defined. Otherwise, you can use the id property. You don’t even need the #{id: } syntax to retrieve it; it automatically sets the jsId property to the same as the ID when generating the REST service on the client side.

The Extension Library book highlights a useful feature in the save() call. You can pass in a callback function that will be executed if there’s an error saving the change. This is helpful to inform the user that their update was not successful. Here’s an example:

var saveArguments = {onError: function() {alert('There was an error saving your changes.');}};
restServiceID.save(saveArguments);

Canceling the Changes

To cancel the any changes that have been made, add another button that calls the revert() method of the REST service:

restServiceID.revert();

REST Service Type

It is very important to note that, by default, the grid’s REST service needs to be a viewItemFileService in order to allow updates. In my testing, the error callback function was always triggered when trying to save updates to the viewJsonService.

However, if you have a Web Site document set up, you can include Put in the list of Allowed Methods in order to enable it to work. (Thanks to Per Lausten for the tip.)

autoHeight Required

I’m not sure why, but I was unable to get this to function properly until I set the autoHeight property on the Dojo Data Grid (All properties > basics > autoHeight).

Without it, the cell would change to edit mode, but it would stay in edit mode if I clicked out of the cell or hit the key. If I tabbed out or hit the key, it would change back to read mode and lose the value. Either way, it would not send any updates when I triggered the save.

Once the autoHeight was set, the editable cell would automatically change back to read mode (with the updated value), no matter what I did to exit the cell — and the updates were saved successfully.

Updatable Columns

You can only send back changes to columns in the REST service that map to single fields. It cannot process updates to computed columns.

Column Editor Types

When you set a column to be editable, it defaults to a plain text editable field, but there are a few additional options in the cellType property:

Grid11_3

Cell is a plain text field (the default type).

Select is a drop-down list. You can compute the options with server-side JavaScript.

Grid11_5

Grid11_6

Bool – provides a checkbox. Just be aware that it’s a little strange to use, because it looks like you can click on the checkbox directly, but you still have to double-click in the cell and then click the checkbox in order to change it.

Grid11_7

AlwaysEdit isn’t a field type per se; it just makes the column always exist in edit mode (as a plain text field).

RowIndex has nothing to do with editing the column. It overrides the data in the column and displays a row counter.

Single Click Editing

The Grid’s singleClickEdit setting defines whether editable columns can be placed into edit mode by a single click. By default, it requires a double-click.

Grid11_8

Grid Updating and Security

You will be happy to know that grid updating respects Author access. The editing happens client-side, so the user can change values in editable columns, but changes will not be saved if the user does not have the proper access to the document. It will trigger the error callback function.

Up Next

In the next post, I’ll show how you can highlight all updated rows in order to have a visual indicator of what has been changed.

Dojo Data Grid – Part 10: Full-text Search and Field-Specific Filtering

So far, we’ve built a grid that scrolls infinitely and can be sorted on specified columns. The next step will be to add the ability to search the view and filter it by specific fields.

Dojo Data Grid Series

Full-Text Searching

Full-text searching works the same way as it does with other view controls, such as the view panel and data view. There’s a search property, but you could also use the keys, startKey, or categoryFilter properties, depending on your needs and your data.

It just takes 3 steps:

  1. Create a search field (Edit Box) and bind it to a scope variable
  2. Bind the Search property of the REST service (All Properties > basics > service > search) to the same scope variable
  3. Create a button that just triggers a partial refresh on the grid and the REST service (ideally, a panel that includes both)

Grid10_1

Set the Grid Height

One interesting side effect of setting the Search property of the REST service is that it makes the grid not display any rows, even though you can look at the REST service and see that it has data.

Grid10_2

Setting the autoHeight property of the grid (All Properties > basics > autoHeight) will take care of this problem. (I’m guessing you could also set it with CSS, but I haven’t tried that yet.)

Field-Specific Filtering

To make this even more user-friendly, you can provide the ability to filter based on specific fields.

To do so, add several fields (they don’t need to be bound to anything) and put code like this on the Search button, which also needs to refresh the REST service and the grid:

Grid10_3

This code just builds the search string, including the ‘AND’ clauses as needed. It also appends the search wildcard (*) to each string to provide results that are more in line with what the users expect. Without that, they would only get results for fields that exactly match the search string. This way, they will get results for field values that start with the search string. You can tweak this as needed — even by adding your own option to check ‘Starts With’, ‘Contains’, ‘Ends With’, etc. You can build this as complex as you need it.

The Clear button just sets the search string scope variable to an empty string and refreshes the REST service and grid.

Note: You cannot compute this in the search property of the REST service because it doesn’t work. It must be too late in the process to perform and then execute the search. This is why the search string is built by the button and stored in the scope variable and the REST service’s search parameter reads it from the scope variable.

grid.filter()

There’s a filter() method of the grid, but I’m not covering it here because it’s only defined to be a client-side solution, so it wouldn’t filter the full set of data from the view (but rather just what’s visible in the client). I was unable to verify this because it wouldn’t work for me at all anyway.

Cannot Sort Search Results

Even if you have sorting enabled and working in the grid, it will not do anything when you sort while the results have been filtered.

Up Next

In the next post, we’ll look at how to edit data directly in the grid!

Dojo Data Grid – Part 9: Multi-Row Entries

Up to this point, our grids have been standard tabular structures displaying a single row for each entry, but it’s possible to span multiple rows with each entry. In this post, I’ll show you how to implement it and work with the formatting.

Dojo Data Grid Series

Dojo Data Grid Row Control

Along with the Dojo Data Grid control and the Dojo Data Grid Column control, there is a Dojo Data Grid Row control, which is optional to use when laying out a grid.

Grid9_1

Once you add one or more row controls to the grid, you can add columns inside of them. The row control contains an editable area into which you can drag and drop column controls.

However, it is important to note that if you use row controls, all columns must be inside of a row control or they will not all display. When I have a few columns displayed directly in the grid control and a few more in a row control, the ones in the row control are not displayed in the grid.

For the sake of comparison, this screen shot shows a grid that renders exactly the same with and without a row control.

Grid9_2

Example

To have an entry span multiple rows, just add multiple row controls and add columns to them. Here’s an example of displaying a person’s name on one line and their address on a second line:

Grid9_3

The grid stacks the column headers and displays the data like this:
Grid9_4

But there’s supposed to be 4 columns in the second row!

Grid Alignment Caveat

It appears that the grid will show as many columns as are in the first row. There should have been 4 columns in the second row, but they aren’t displayed. (I tested changing the auto-width attribute and setting column widths, but that had no effect on the outcome.)

If I flip the rows so that the address row (with more columns) comes first, then I see all of the data:

Grid9_5

Example 2

If I want to take my example a step further and make it look more like a mailing label, I can add another row and move the city, state, and zip down to the third row.

However, that still leaves me with 3 columns in the last row and only two columns in the first row. The good news is that I found that I can just add an empty column to the first row in order to provide space for the third column in the last row.
Grid9_8

Grid Column Widths

If you specify column widths in the same column in multiple rows, the width set on the column in the first row will take precedence.

Spanning multiple cells

In this case, we would really want the address line to take up more space, so we could condense the column widths and make the grid entries look more natural.

When you add multiple rows to a grid, it generates a table inside the <div>for the entry to provide the multiple row layout:

Grid9_6

Even though I can see that each cell has a colspan attribute, I was unable to find an easy way to tell the column in the second row to span multiple columns. I tried adding attributes and dojo attributes in the source. I used the themeId property and tried to pass settings that work on plain tables, but they were not used by the grid. (It appears that when the dojo code runs to build the grid, it loses the theme ID, because none of the properties are picked up.)

Fortunately, there is a solution. The grid has an onStyleRow event that fires on each row (a) when the grid is created and (b) when you interact with the row (eg hover over it, etc).

Code in this method automatically receives an object that can be accessed via arguments[0] and it provides these properties: index, node, odd, selected, over, customStyles, customClasses. The index is the grid entry index. The odd property is true for every odd-numbered row. The selected and over properties track the state of the row (selected or mouse hover). The customStyles and customClasses properties allow you to dynamically change the styles and/or classes of the row.

The node property is a handle to the DOM node for the grid entry. This is great, because it gives us a starting point to look for any table cells that we need to modify.

In this case, I want the table cell (td) in the second row of the entry to have a colspan of 3. I can locate and modify the setting with this line of dojo code:

dojo.query("tr:nth-child(2) td", arguments[0].node).forEach(function(node) {node.colSpan="3";});

So, I just need to add it to the onStyleRow event of the grid and it will execute on every grid entry.

But, be careful how you add the code. If you select the grid and then go to the Events view and enter code in the onStyleRow event, it doens’t add it to the page properly. It adds this:

<xe:eventHandler event="onStyleRow" submit="false">
  <xe:this.script><![CDATA[dojo.query("tr:nth-child(2) td", arguments[0].node).forEach(function(node) {node.colSpan="3";});]]></xe:this.script>
</xe:eventHandler>

Make sure you select the grid, use the Properties view >> All Properties > events > onStyleRow and click the button in that property to enter the code and it will add it properly like this:

<xe:this.onStyleRow>
  <![CDATA[dojo.query("tr:nth-child(2) td", arguments[0].node).forEach(function(node) {node.colSpan="3";});]]>
</xe:this.onStyleRow>

You can see that the address line now spans 3 columns in the screen shot on the left (the screen shot on the right is the default alignment):
Grid9_7

Caveat to adding onStyleRow code

When you add any code to the onStyleRow even, you lose the default styling for every other row and when a row is hovered over.

You can easily replace that style logic by making use of the odd property and over event state of the object in onStyleRow and adding a class or setting the style directly.

Multiple Rows and Sorting

Breaking up grid entries across multiple rows has no bearing on sorting. You can still click column headers to sort and it works just fine.

Up Next

In the next post, we’ll look at full-text and field-specific searching to filter the grid results.

Dojo Data Grid – Part 8: Opening Documents

So far, our grid has been read only, but, invariably, your users will need to open documents. This post shows how to get the selected row and obtain the unid needed to build the url to open the document.

Dojo Data Grid Series

Grid Events

There are several events on the Dojo Data Grid. Of particular note when adding the ability to open a document are the onRowClick and onRowDblClick events.

You can put code on either of these events to open a document.

Getting the selected unid

When you write code in one of these events, an object is passed in with a lot of properties and you can access it via arguments[0].

It has all of these properties available:

rowNode, rowIndex, dispatch, grid, sourceView, cellNode, cellIndex, cell, type, target, currentTarget, eventPhase, bubbles, cancelable, timeStamp, defaultPrevented, stopPropagation, preventDefault, initEvent, stopImmediatePropagation, which, rangeParent, rangeOffset, pageX, pageY, isChar, screenX, screenY, mozMovementX, mozMovementY, clientX, clientY, ctrlKey, shiftKey, altKey, metaKey, button, buttons, relatedTarget, mozPressure, mozInputSource, initMouseEvent, initNSMouseEvent, getModifierState, layerX, originalTarget, explicitOriginalTarget, preventBubble, preventCapture, getPreventDefault, isTrusted, view, detail, initUIEvent, layerY, cancelBubble, NONE, CAPTURING_PHASE, AT_TARGET, BUBBLING_PHASE, MOUSEDOWN, MOUSEUP, MOUSEOVER, MOUSEOUT, MOUSEMOVE, MOUSEDRAG, CLICK, DBLCLICK, KEYDOWN, KEYUP, KEYPRESS, DRAGDR, FOCUS, BLUR, SELECT, CHANGE, RESET, SUBMIT, SCROLL, LOAD, UNLOAD, XFER_DONE, ABORT, ERROR, LOCATE, MOVE, RESIZE, FORWARD, HELP, BACK, TEXT, ALT_MASK, CONTROL_MASK, SHIFT_MASK, META_MASK, SCROLL_PAGE_UP, SCROLL_PAGE_DOWN, MOZ_SOURCE_UNKNOWN, MOZ_SOURCE_MOUSE, MOZ_SOURCE_PEN, MOZ_SOURCE_ERASER, MOZ_SOURCE_CURSOR, MOZ_SOURCE_TOUCH, MOZ_SOURCE_KEYBOARD

The rowIndex property provides the row number that was clicked. The grid property provides a handle to the grid.

In the Extension Library book, there’s an example that shows using the rowIndex and looking up information from the REST service based on that index, but using the _items property of the REST service didn’t seem to work for me, so I had to approach it from a different angle. I believe it is because I’m using the viewJsonService REST service as opposed to the viewItemFileService REST service.

The output from the viewJsonService REST service is on the left in the image below and the output from the viewItemFileService REST service is on the right. The viewItemFileService provides the items object to reference the entries.

Grid8_1

My Solution

Fortunately, you can get the information needed from the grid itself. The code below works to open the document from the grid.

I’ve included a few comments with additional options for obtaining the same information in another way.

var grid = arguments[0].grid;
// ^^ Another Option: var grid = dijit.byId('#{id:djxDataGrid1}');
var index = arguments[0].rowIndex;
// ^^ Another Option: var index = grid.selection.selectedIndex;
var item = grid.getItem(index);
var unid = item["@unid"];
var url = "XPage.xsp?documentId=" + unid +"&action=openDocument";
window.document.location.href = url;
// ^^ Another Option: window.open(url, 'docWindow');

To use this in your application, just change the url variable to start with the proper name of your XPage to display the document.

Up Next

In the next post in this series, we’ll take a look at adding and aligning multi-row entries in the grid.

Dojo Data Grid – Part 7: Sorting

The Dojo Data Grid does its best to provide sorting options by default. Strangely enough, it takes more work to prevent sorting! This post will cover how to allow the grid to sort columns and how to prevent sorting of some columns or directions.

Dojo Data Grid Series

Default Sorting Features

When you surface a grid, it automatically attempts to provide the ability to sort any column ascending or descending. When you click on a column header, it displays a triangle pointing upward and attempts to sort that column in ascending order. When you click the column header again, it displays a triangle pointing downward and attempts to sort that column in descending order.

On every click, the data disappears and the grid is refreshed. But nothing happens on many of the column clicks!

This is because each sorting option can only work if the underlying view column already has that sort option (and, therefore, the view has an index to support that sorting option).

The sort options are provided on the Click on column header to sort properties of the underlying view columns:
Grid7_1

If your underlying view already has every column set to allow both sorting directions, then you can leave it as is and it will work like a charm.

However, if your view does not have those options set — and it is valid not to have that set because every potential sorting option requires the view to maintain another index — then you can disable sorting options with a little bit of client-side JavaScript coding.

It’s worth the effort to prevent the confusion of users seeing arrows and grid refreshes, but not seeing any difference in the data.

Preventing Specific Sorting Options

The dojo data grid has an attribue named canSort which defines a function that is called when the user clicks on a column header. It accepts a number, which is the index of the column, and returns true or false, based on whether the sorting option is allowed.

It is a one-based index (meaning, the first column is 1, the second column is 2, and so on).

The other important thing to note is that the function will be called with a positive number if an ascending sort is requested and a negative number of a descending sort is requested. So, the function will receive a 1 if it is attempting to sort the first column in ascending order, but it will receive -1 if it is attempting to sort the first column in descending order.

Attaching the canSort function

You can use dojo to attach the function to the canSort attribute of the grid and pass it a function that will return true or false.

The function will automatically receive a single parameter with the column index. The name of the parameter does not matter.

Run the code in the onClientLoad event of the page or custom control containing the grid.

Examples

This function will prevent column 3 from being sortable. The Math.abs() function is used to return the absolute value of the index passed in, meaning it will return a positive number regardless of whether the index passed in is positive (ascending sort) or negative (descending sort):

dijit.byId('#{id:djxDataGrid1}').canSort = function(col){
  if(Math.abs(col) == 3) {
    return false;
  } else {
    return true;
  }
};

This function will prevent all column sorting:

dijit.byId('#{id:djxDataGrid1}').canSort = function(col){
  return false;
};

This function will prevent all descending sorting:

dijit.byId('#{id:djxDataGrid1}').canSort = function(col){
  if (col < 0) {
    return false;
  } else {
    return true;
  }
};

This function will only allow even numbered columns to be sorted:

dijit.byId('#{id:djxDataGrid1}').canSort = function(col){
  if (col % 2 == 0) {
    return true;
  } else {
    return false;
  }
};

Caveat with Column Reordering

This logic is all based on the column index. However, if you allow users to reorder the columns in the grid, you may have unexpected results with the canSort logic.

For example, if my canSort function prevents sorting column 3, but I move that column ahead of the second column, then I’ll be able to try to sort the column, since it’s at a different position, and I’ll lose the sorting option for column 2, because it is now column 3.

This isn’t going to cause any errors, but there is the potential for confusion, so keep that in mind.

Sorting Compared to ExtJS

It’s interesting to note the difference in how sorting works in ExtJS, which described in this post by Mark Roden.

In the Dojo Data Grid, all sorting is done remotely, unlike ExtJS, where it has the built-in feature to sort the current page of data locally (i.e. without requiring a call to the REST service). The Dojo Data Grid doesn’t do any paging, so it does a full sort on the data every time. The advantage is that the results are always what the user expects, but the disadvantage is that it requires a server round trip and a refresh of the grid.