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

Dojo Data Grid – Part 2: Providing the Data with a REST Service

$
0
0

Data grids need a data store to obtain the information to display in the grid.

An easy way to set one up with XPages by using a REST Service control, which, like the Dojo Data Grid control, is available in the Extension Library and with 8.5.3 Upgrade Pack 1. You can find it in the Data Access section of the Controls view in Domino Designer:

Grid2_1

When you add one to the page, you see this little icon on the Design tab:

Grid2_2

Configuring the REST service

Follow these steps to set up the REST service to provide the data from a view:

1. Select the REST service control and All Properties subtab of the Properties panel, go to basics > service and click the plus (+) icon and select xe:viewJsonService
Grid2_3

2. Set the contentType (basics > service > contentType) to application/json

3. Set the defaultColumns property (basics > service > defaultColumns) to true. This provides the data from all columns in the underlying view, without you having to define them individually. (You can select individual columns or define your own via the columns property, if you choose, but this is the easiest way to get all columns from the view.)

4. Select the target view in the viewName property (basics > service > viewName)

Verifying the REST service

If you set the pathInfo property of the rest service (All Properties > basics > pathInfo), you will have a way to verify the data being returned by the service. This is a very helpful tool not only for troubleshooting, but for gaining an understanding of the data structure that you’re working with.

Grid2_5

To verify the data, load the XPage that contains the REST service and include the pathInfo name in the URL, like this:

server.com/dbname.nsf/xPageName.xsp/pathInfoName

Here is an example of what you will see:

Grid2_6

The pathInfo is not required when working with the Dojo Data Grid, but it is required when consuming the REST service from another library, like ExtJS.

Creating a Custom Column

In addition to (or in lieu of) the default columns, you can define your own custom columns for the REST service. You can use this to compute values that combine information in the underlying view or even look up information from another database altogether, because you use server-side JavaScript.

In order to read data from the view entry, you will need to set the ‘var’ property of the REST service.

For example, if I wanted to add a column that combines the firstname and lastname columns from underlying view, I would need to take these steps:

  1. Set the var property of the REST service (All Properties > basics > service > var)
  2. Add a new column, but clicking the ‘+’ icon in the columns property (All Properties > basics > service > columns)
  3. Click the diamond to compute the value of the column (All Properties > basics > service > columns > restViewColumn[0] > value)
  4. Set the name of the column (this will be the way to reference the column in the grid) under All Properties > basics > service > columns > restViewColumn[0] > name
  5. Enter a script like this:
return restEntry.getColumnValue("firstname") + ' ' + restEntry.getColumnValue("lastname");

Grid2_7

Now, if you verify the REST service data, you’ll see the additional column.
Grid2_8

There does not appear to be a built-in way to get a handle to the underlying document, but you should be able to reference the unid from the view entry and use that to look up the document, should you need more information.

System Columns

The REST service also has a systemColumns property, which allows you to select system values to include with each entry. Clicking the button in the property row will bring up a dialog box with checkboxes for the options to select.

Grid2_4

Here are the options:

  • NoteID
  • UNID
  • Position
  • Read
  • Siblings
  • Descendents
  • Children
  • Indent
  • Form
  • Category
  • Response

As you can see in the screen shots earlier in this post (with the REST service data), several of these system columns are included by default: unid, noteid, position, siblings, form.

If you select one or more system columns specifically, then only the selected system columns will be included. It appears that @entryid will always be included.

Ready for the grid!

How that you have a REST service providing data, you are ready to surface the grid! Tune in next time to see how to surface your first grid.



Dojo Data Grid – Part 3: Creating Your First Grid

$
0
0

In the series overview, I mentioned that there are 3 high-level steps to creating a Dojo Data Grid. In the last post, I showed how to create the REST service to provide data for the grid. In this post, I’ll show how to create the grid and add columns to it.

Dojo Data Grid Series

Creating the Grid

With the Extension Library or Domino Designer 8.5.3 Upgrade Pack 1, you will find the Dojo Data Grid control in the Dojo Layout section of the Controls view in DDE.

Grid_4_1

Just drag it onto an XPage or Custom Control and you’ll see it on the page with a single facet. This is where you add columns to display data in the grid.

Grid_4_2

At this point, if you view the page, there will be nothing displayed, because there is no data for the grid.

Connecting the Grid to the REST Service

To provide data for the grid, you can connect the grid to the REST service that will provide its data.

To do so, set the storeComponentId property of the grid properties (All Properties > data > storeComponentId) to the ID of the REST service. (This is the ID of the REST service and not the pathInfo value.)

Grid_4_3

Adding Columns to the Grid

All that’s left now is to tell the grid what to display.

To add a column to the grid, just drag and drop a Dojo Data Grid Column control (from the Dojo Layout section of the Controls view) into the Dojo Data Grid facet and set its field property to the name of a field in the REST service.

If you are using default columns in the REST service, then the field name will match the programmatic name of the column from the underlying view. If you have created your own columns for the REST service, then the field name will match the name property of the column that you defined. See my last post on Part 2: creating a REST service to learn how to verify the REST service data and see the field names.

Grid_4_4

Hello, Grid

Now you can view the page and see that you’ve successfully surfaced your first grid!

Grid_4_5

You can change the column width and scroll infinitely through the data.

To fill out the grid, just add more Dojo Data Grid Column controls and set their field properties.

Up Next

In the next post, I’ll dig in to more features of the grid control that are readily available.


Dojo Data Grid – Part 4: Additional Grid Features

$
0
0

In the last few posts, I showed the steps required to surface a Dojo Data Grid based on data provided by a REST service. This post explores some of the additional properties of the Dojo Data Grid control.

Dojo Data Grid Series

Let’s take a look ta some of the effects of some of the available grid properties, in no particular order.

Grid Width

By default, the grid will take up the entire width of its container.

Grid4_BeforeAutoWidth

There isn’t a property to change this per se, but you can add a dojo attribute to the grid (All Properties > dojo > dojo attributes) with the name autoWidth and set its value to true to have the grid size itself based on the width required.

Grid4_autoWidthProperty

Grid4_AutoWidth

The initialWidth property (All properties > format > initialWidth) will set the width of the grid, but it will only take effect if the dojo attribute autoWidth is enabled. The values must be specified with a unit (such as 500px, rather than just as 500), in order to work.

Row Selector

The rowSelector property (All properties > format > rowSelector) defines the size of a row selection column to the left of each row, which can be used to select the row. The value must specify a unit as well, like the initialWidth property.

Grid4_rowSelectorProperty

Grid4_rowSelector

Passing Through HTML Content

The escapeHTMLInData property (All properties > basics > escapeHTMLInData) defines whether HTML content in the grid data can be passed through to the browser. By default, HTML content is escaped.

Loading Message

The loadingMessage property (All Properties > basics > loadingMessage) defines the message to display at the top of the grid when it is loading data.

Grid4_Loading

If you don’t set this property, it defaults to “Loading…”.

Error Message

The errorMessage property (All Properties > basics > errorMessage) defines the message to display when the grid runs into an error retrieving data.

Grid4_ErrorMsg

If you don’t set this property, it defaults to “Sorry, an error occurred…”

Auto Height

The autoHeight property (All Properties > basics > autoHeight) defines the number of rows to display in the grid. It will still scroll infinitely regardless of this setting.

The behavior seems a little odd to me. I would expect that it would display the number of view entries that I specify, but I noticed that it shows less if there is data in a grid cell that wraps. However, I don’t see an exact correlation to the number of rows it’s displaying, even counting some entries as multiple rows. I also noticed that if I stretch the grid cells so that there is no data wrapping, the number of rows displayed slightly higher than the number of rows I’ve specified.

And the number of rows displayed seems to change as I make columns wider and narrower.

At this point, I’ll just assume this is a general target for the grid. To ensure the height, it might be wiser to fix the size of the containing the grid.

Rows Per Page

The hover help for the rowsPerPage property (All Properties > basics > rowsPerPage) says that it specifies the number of rows to display. However, it seems to have no bearing whatsoever on the number of rows to display.

It appears that it actually defines the number of rows to retrieve from the REST service with each call. I’ve noticed that the number specified in this property seems to be number of rows refreshed into the grid at a time. It will quickly fill in the entire page, but I can see it refreshing in chunks of rows equal to this number.

dir

The dir property (All Properties > basics > dir) defines the directional display of the grid. It defaults to display Left to Right, but it’s interesting to note how easily it can be reversed. And the setting can be computed, so keep that in mind if using a grid in a multilingual application.

One interesting quirk, though, is that it seem so to lose the column headers when the direction is set to Right to Left.

Grid4_Dir

Up Next

In the next post, we’ll take a closer look at properties of the Dojo Data Grid Column controls to see how they can be customized.


Dojo Data Grid – Part 5: Grid Column Features

$
0
0

To surface a column in a Dojo Data Grid, all you need to do is drag a Dojo Data Grid Column control into the Dojo Data Grid control and set its field property. In this post, I’ll dig into some additional properties of the column and also show how to set an attribute prevent resizing.

Dojo Data Grid Series

Column Properties

This screen shot shows the available properties for the Dojo Data Grid Column control:
Grid5_1

Column Label

The label property sets the column header text. It overrides the default value, which is the name of the field in the REST service (which, in turn, defaults to the programmatic name of the underlying view column, if it’s not a custom column).

Column Width

The width property sets the column width. If not set, it will be automatically sized when rendered.

Formatting Column Data

The formatter property accepts the name of a client-side JavaScript function that will format the column data.

For example, if you wanted the firstname column to be in all upper case, you could include this code in an Output Script block and set the formatter property of the column to allUpper

function allUpper(value) {
  return value.toUpperCase();
}

Grid5_2

Hiding Columns

The hidden property determines whether the column is displayed. You can compute the value conditionally with SSJS if you need it to be based on the current user or some other piece of contextual information.

Editable Column Properties

The cellType, editable, and options properties are all related to editing column values directly in the grid. The cellType property generally defines the data type for the cell when being edited. The editable property is part of determining whether the cell is editable (other factors are involved), and the options property defines the options if the cell uses a drop-down list when editable.

I’ll cover these properties more thoroughly in a future post about editing data in the grid.


Article Published in The View: Implementing Editable View Columns in XPages

Dojo Data Grid – Part 6: Reordering Columns

$
0
0

Column reordering is a useful feature of a grid. If you provide the user the ability to rearrange the columns, you can create less views and grids in the application. There’s no property built into the dojo data grid to allow the user to reorder the columns, but it’s easy to add with a dojo attribute.

Dojo Data Grid Series

Reordering Columns

By default, Dojo Data Grid columns cannot be reordered, but it is an available feature of the dojo grid. All it requires is for the grid to have an attribute of columnReordering set to true. Fortunately, there is a way to add a dojo attribute.

Under All Properties > dojo > dojoAttributes, you can add a dojo attribute, set its name to columnReordering and set it’s value to true.

Grid6_1

This is what the attribute looks like in the page source:

<xe:this.dojoAttributes>
  <xp:dojoAttribute name="columnReordering" value="true"></xp:dojoAttribute>
</xe:this.dojoAttributes>

Now, if I want the lastname column to come before the firstname column in my grid, all I have to do is click on the column header and drag it to the left.

Grid6_2

And now the order has been changed.

Grid6_3

Up Next

In the next post, I’ll show what is required for sorting the grid and preventing sorting of specific columns or specific sort directions.


Dojo Data Grid – Part 7: Sorting

$
0
0

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.


Dojo Data Grid – Part 8: Opening Documents

$
0
0

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 9: Multi-Row Entries

$
0
0

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 10: Full-text Search and Field-Specific Filtering

$
0
0

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 variables
  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 11: Editing Data in the Grid

$
0
0

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 12: Highlighting Edited Rows

$
0
0

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) &gt; -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 13: Create a Dojo EnhancedGrid

$
0
0

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/resources/EnhancedGrid.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 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 14: Enhanced Filtering with No Coding!

$
0
0

Dojo EnhancedGrid plugins take the functionality of the grid to a whole new level. In this post, we’ll look at the tremendous Filter plugin, which allows users to create multiple filtering rules with many matching options — all with no coding whatsoever!

Dojo Data Grid Series

Start with a Dojo EnhancedGrid

Before you can use an EnhancedGrid plugin, you need to be set up the Dojo Data Grid control to render as a Dojo EnhancedGrid. Follow the steps in this post to get ready.

1. Load the Filter Plugin

The dojox.grid.enhanced.plugins.Filter module must be included on the page, so add it to the page resources. Properties > Resources > Add > Dojo Module…
Dojo_14_1_AddFilterModules

2. Add the Plugin to the Grid

The first step made the plugin module available, but you also need to add it to the grid.

Go to the properties of the Dojo Data Grid > Dojo and click the Add button. Add the property as shown here:
Dojo_14_2_AddPluginProperty

3. Add a style sheet

In addition to the style sheets listed in the previous post, you need an additional style sheet for the filter bar and filtering dialog to format properly.

Add this style sheet to the page resources as well:

/.ibmxspres/dojoroot/dojox/grid/enhanced/resources/claro/EnhancedGrid.css

All together, the resources of the page should look like this:

<xp:this.resources>
  <xp:dojoModule name="dojox.grid.EnhancedGrid"></xp:dojoModule>
  <xp:dojoModule name="dojox.grid.enhanced.plugins.Filter"></xp:dojoModule>
	
  <xp:styleSheet
    href="/.ibmxspres/dojoroot/dojox/grid/resources/Grid.css">
  </xp:styleSheet>
  <xp:styleSheet
    href="/.ibmxspres/dojoroot/dojox/grid/resources/EnhancedGrid.css">
  </xp:styleSheet>
  <xp:styleSheet
    href="/.ibmxspres/dojoroot/dojox/grid/enhanced/resources/claro/EnhancedGrid.css">
  </xp:styleSheet>
</xp:this.resources>

Filtering Enabled

When you load the grid, you will now see the filter bar appearing directly below the column headers.
Dojo_14_3_FilterBar

Filtering the Grid

To filter the data, click on the icon on the left of the filter bar in order to pop up the filtering dialog.
Dojo_14_5_FilterDialog

FWIW – This is what it looks like without that extra style sheet:
Dojo_14_4_FilterPopup_NoStyleSheets

Filter Rules

You can set the filtering to match all or any of the specified rules.

For each rule, you select a column and condition and enter a value.

The Column options are ‘Any Column’ or any specific column in the grid. (You can add properties to grid columns to prevent them from being filterable.)

The Condition option determines how it searches the column. It defaults to ‘contains’, but has many other options for matching the data.
Dojo_14_6_FilterConditions

Multiple Rules

You can add up to 3 filtering rules. The dialog provides a nice accordion interface to modifying the rules.
Dojo_14_7_MultipleRules

Results

When you click the button to execute the filter, the grid is updated with the results. It gives you a count of items and the total that were searched. It also provides a link to clear the filter and display the full data set.
Dojo_14_8_FilterResults

Bonus — It’s Sortable!

You can even sort the filtered results.

I am thoroughly impressed that this works so well with so little effort. Keep an eye on performance, though, because it has to hit the server to filter the data.

REST Service Type – Important

The filtering seems to work well when the grid is bound to a viewItemFileService rest service. It does not work with a viewJsonService service.

More Information

For more information about the Filtering plugin, check out the documentation.


Dojo Data Grid – Part 15: EnhancedGrid Printing

$
0
0

In the last post, we looked at the Dojo EnhancedGrid plugin for filtering. In this post, we’ll look at the EnhancedGrid Printer plugin, which provides the ability to preview and print the grid contents.

Dojo Data Grid Series

Print and Preview Functionality

The Printer plugin comes with three print options and three corresponding preview options:

  • Preview/Print All
  • Preview/Print Selected
  • Preview/Print Custom

Start with a Dojo EnhancedGrid

Before you can use an EnhancedGrid plugin, you need to be set up the Dojo Data Grid control to render as a Dojo EnhancedGrid. Follow the steps in this post to get ready.

1. Load the Printer Plugin

The dojox.grid.enhanced.plugins.Printer module must be included on the page, so add it to the page resources. Properties > Resources > Add > Dojo Module…

DojoDataGrid_15_0a_PluginModule

2. Add the Plugin to the Grid

The first step made the plugin module available, but you also need to add it to the grid.

Go to the properties of the Dojo Data Grid > Dojo and click the Add button. Add the property as shown here:

DojoDataGrid_15_0b_PluginAttribute

3. Add Printing and Preview Functions

Next, you need to add the client-side JavaScript functions that will execute the desired print and preview functionality. You can put them in an Output Script tag or a client-side JavaScript library to make them available on the page.

These functions were taken directly from the plugin documentation and modified slightly to work within XPages.

var gridName = "#{id:djxDataGrid1}";

function printAll(){
  dijit.byId(gridName).printGrid({
    title: "Grid Print - All"
  });
}

function printSelected(){
  dijit.byId(gridName).printSelected({
    title: "Grid Print - Selected"
  });
}

function printCustomized(intStart, intCount){
  dijit.byId(gridName).printGrid({
    title: "Grid Print - Customized",
    fetchArgs: {
      start: intStart,
      count: intCount
    }
  });
}

function preview(str){
  var win = window.open();
  win.document.open();
  win.document.write(str);
  /*Adjust row height/view width for multi-view grid*/
  dijit.byId(gridName).normalizePrintedGrid(win.document);
  win.document.close();
}

function previewAll(){
  dijit.byId(gridName).exportToHTML({
    title: "Grid Print - All"
  }, preview);
}

function previewSelected(){
  preview(dijit.byId(gridName).exportSelectedToHTML({
    title: "Grid Print - Selected"
  }));
}

function previewCustomized(intStart, intCount){
  dijit.byId(gridName).exportToHTML({
    title: "Grid Print - Customized",
    fetchArgs: {
      start: intStart,
      count: intCount
    }
  }, preview);
}

The primary changes I made were setting a variable with the client-side ID of the grid and setting the preview and print custom functions to accept variables, rather than use hard-coded values.

4. Add buttons to call the printing functions

Once the functions are ready, you can add buttons (or images or links) with client-side JavaScript code to call the print and preview functions.

Here is the source for the buttons on my sample page:

<xp:button value="Print All" id="button1">
  <xp:eventHandler event="onclick" submit="false">
    <xp:this.script><![CDATA[printAll()]]></xp:this.script>
  </xp:eventHandler>
</xp:button>
<xp:button value="Print Selected" id="button2">
  <xp:eventHandler event="onclick" submit="false">
    <xp:this.script><![CDATA[printSelected()]]></xp:this.script>
  </xp:eventHandler>
</xp:button>
<xp:button value="Print Customized" id="button3">
  <xp:eventHandler event="onclick" submit="false">
    <xp:this.script><![CDATA[printCustomized(200, 30)]]></xp:this.script>
  </xp:eventHandler>
</xp:button>
<xp:button value="Preview All" id="button4">
  <xp:eventHandler event="onclick" submit="false">
    <xp:this.script><![CDATA[previewAll()]]></xp:this.script>
  </xp:eventHandler>
</xp:button>
<xp:button value="Preview Selected" id="button5">
  <xp:eventHandler event="onclick" submit="false">
    <xp:this.script><![CDATA[previewSelected()]]></xp:this.script>
  </xp:eventHandler>
</xp:button>
<xp:button value="Preview Customized" id="button6">
  <xp:eventHandler event="onclick" submit="false">
    <xp:this.script><![CDATA[previewCustomized(200, 30)]]></xp:this.script>
  </xp:eventHandler>
</xp:button>

Default Browser Print Format

By default, the browser’s Print option prints exactly what you see on the page.
DojoDataGrid_15_1_BrowserPrint

Now, let’s take a look at the output from the Printer plugin.

Preview/Print Selected

With the enhanced grid, you can SHIFT+Click to select a set of rows and you can CTRL+Click to select non-contiguous rows. These options allow you to only print selected rows.
DojoDataGrid_15_2a_SelectRows

DojoDataGrid_15_2b_PreviewSelected

Preview/Print Custom

I think this is the best feature. It will start with the specified row and include the specified number of rows thereafter. I updated the code above to make these dynamic, so your print function can pass in variables for the start row and number of rows.

Preview/Print All

Quite frankly, these options are misleading. In my limited testing, they seem to always only include the first chunk of rows in the grid, no matter how far you have scrolled.

Formatting the Output

You can add a cssFiles property to any of these print functions to apply your own style sheet to format the output.

function printAll(){
    dijit.byId("grid").printGrid({
        title: "Grid - All",
        cssFiles: cssFiles
    });
}


Dojo Data Grid – Part 16: Exporting Grid Data

$
0
0

Exporter is another useful plugin available to for the Dojo EnhancedGrid. As the name indicates, it gives you the ability to export grid data. In this post, I’ll show how to implement it to export grid data in CSV format.

Dojo Data Grid Series

Exporter Plugin

The Exporter plugin is a bit tough to get a handle on — it requires more work than most plugins to get up and running.

Essentially, what it does is provide a string variable with the data to export. You have to take it from there and actually export the data.

Once you get it up and running, there’s an API full of methods that you can override in order to customize the output, but that’s beyond the scope of this post.

Using the Plugin

This post starts with the assumption that you have already have a working Dojo Data Grid control (with a REST service supplying the data) and that you have followed the instructions in this post to ensure that your Dojo Data Grid control is actually generating an EnhancedGrid, rather than a standard DataGrid.

From that point, there are two more steps to use the Exporter plugin.

1. Add the CSVWriter module

Grid_16_config1

2. Include the plugin on the grid

Grid_16_config2

Export Functions

At this point, there are two functions available to get you started with exporting data: exportGrid() and exportSelected().

The exportGrid function will export the number of records defined in the REST service’s count property. If a count is not defined,it will export 10 rows.

Fortunately, it also takes a fetchArgs property that allows you to define the starting row and number of rows to export. The example below has those values set up as parameters to the function so you can work with them dynamically.

The exportSelected function provides a handle to the selected rows in the grid and makes them available to export.

Export Process

As I mentioned earlier, the Exporter plugin functions really just give you data that’s ready to be exported; you have to take it the rest of the way.

Unfortunately, this is not as trivial a process as it might sound.

I’ll demonstrate how I made it work, but I’m sure there are ways to improve upon the process. (See the end of this post for a list of methods that I tried that were unsuccessful.)

My process includes these steps:

  1. Store the grid data in a hidden input field
  2. Use SSJS to put the data into a scope variable
  3. Launch a separate XAgent page to read the data and export it

To export, the user will click the appropriate ‘Export’ button…

Grid_16_a1

…Click OK on the prompt informing them that the export is underway (more on this below)…

Grid_16_a2

…Click ‘Open’ or ‘Save’ when the data is exported…

Grid_16_a3

…And view the data

Grid_16_a4

To export selected rows, the user will select rows and click on the Export Selected button…

Grid_16_b1

Grid_16_b2

Export Functions

The code below contains an Output Script block that defines all three export functions (export, export selected, export custom).

They all use a built-in method from the Exporter plugin to obtain the data to export and then put that data into a hidden input field on the form.

<xp:scriptBlock id="scriptBlock1">
<xp:this.value><![CDATA[
// Call the plugin's exportGrid function and store the data to export in an unbound hidden input field.
function exportGridData_All() {
  dijit.byId("#{id:djxDataGrid1}").exportGrid("csv", function (gridData) {
    dojo.byId("#{id:csvToExport}").value = gridData;
    }
  );
}

// Call the plugin's exportGrid function and store the data to export in an unbound hidden input field.
function exportGridData_Custom(intStart, intCount) {
  dijit.byId("#{id:djxDataGrid1}").exportGrid("csv", {fetchArgs: {start: intStart, count: intCount}}, function (gridData) {
    dojo.byId("#{id:csvToExport}").value = gridData;
    }
  );
}

// Call the plugin's exportSelected function and store the data to export in an unbound hidden input field.
function exportGridData_Selected() {
  var gridData = dijit.byId("#{id:djxDataGrid1}").exportSelected("csv");
  dojo.byId("#{id:csvToExport}").value = gridData;
}

]]></xp:this.value>
</xp:scriptBlock>

Here is the hidden input field that I’m using to store the grid data:

<xp:inputHidden id="csvToExport"></xp:inputHidden>

Export Buttons

Each export button controls all 3 steps of the export process.

  1. With client-side code, the button calls an export method in the output script block to retrieve the data and put it in a hidden input field.
  2. With server-side code, it reads the data from the hidden input field and puts it in a scope variable.
  3. With client-side code on the oncomplete event of the server-side code, it launches a new window with the XAgent page to write out the data.

The ‘Export All’ Button is shown below:

<xp:button value="Export All" id="button1">
  <xp:eventHandler event="onclick" submit="true" refreshMode="partial" refreshId="csvToExport">
    <xp:this.script><![CDATA[
      // Step 1 of 3: Execute the Exporter's export function and use client JS to put the data into an unbound hidden input text field
      exportGridData_All();

      // Pop up an alert box -- this delay provides enough of a delay to let the server-side code read the data from the hidden input field and export it
      alert('Exporting Data...');
]]></xp:this.script>

    <xp:this.action><![CDATA[#{javascript:
      // Step 2 of 3: Read the csv data from the hidden field and put it in a scope variable so the XAgent can retrieve it
      sessionScope.csvExport = getComponent('csvToExport').getValue();}]]>
    </xp:this.action>

    <xp:this.onComplete><![CDATA[
      // Step 3 of 3: oncomplete of the server code that puts the data in the scope variable, open the XAgent page to read and export
      window.open('Grid_16_ExportToCSV_XAgent.xsp');
      dojo.byId("#{id:csvToExport}").value = '';]]>
    </xp:this.onComplete>
  </xp:eventHandler>
</xp:button>

For the ‘Export Selected’ button, the only difference is that the client-side script’s first line (line 5) is this:

exportGridData_Selected();

For the ‘Export Custom’ button, the only difference is that the client-side script’s first line (line 5) is this:

exportGridData_Custom(20, 15);

XAgent Code

The XAgent’s afterRenderResponse event writes out the CSV data. In line 9, it reads the data from the scope variable (line 9) and writes it out in a way that the browser will download it as a CSV file.

// This XAgent just writes out the CSV data from the grid that is stored in a scope variable.
var exCon = facesContext.getExternalContext(); 
var writer = facesContext.getResponseWriter();
var response = exCon.getResponse();
response.setContentType("text/csv;charset=utf-8");

response.setHeader("Cache-Control", "no-cache");
response.setHeader("Content-Disposition", "attachment; filename=GridExport.csv");
writer.write(sessionScope.csvExport);
writer.endDocument();

Beware of Popup Blockers

Be aware that users must grant access to your site to display popups, or else the last step will not work. (If that’s impossible to work around in your environment, you could use location.href=XAgentPage.xsp, rather than window.open).

Further Enhancement

CSV files will generally open just fine in Excel, but, if you need to export the data as an Excel file, you can set the content type to application/vnd.ms-excel and write the data out in the format of an HTML table.

You can also customize the format of the data as it is written out by overriding the available API methods, detailed in the dojo documentation:

http://dojotoolkit.org/reference-guide/1.6/dojox/grid/EnhancedGrid/plugins/Exporter.html

Failed Attempts

This section lists some of the other methods that I attempted to make this work.

Attempt #1
My original intent was to only use client-side JavaScript to write the data out directly.

There’s an encodeURI method in JavaScript. You can actually put encoded CSV data into a URL and launch it and it will attempt to download. But it didn’t work in IE. The other problem is that there doesn’t appear to be a way to define the file name/type, so it kept downloading the data with a .part extension.

location.href='data:application/download,' + encodeURIComponent(csvData)

Attempt #2

In HTML5, there’s a new link property called download that can be used to specify a download filename.

I tried to use code like this to add a link to the page with the required properties and force the click of the link but (a) the link.click() only works in IE and (b) the download property does not work in IE.

var encodedUri = encodeURI(csvContent);
var link = document.createElement("a");
link.setAttribute("href", encodedUri);
link.setAttribute("download", "my_data.csv");
link.click();

Attempts 3-5
Next, I had hoped to use client JS to write the data into a field bound to a scope variable, partially refresh the field to push the data to the scope variable, then launch the XAgent page to pick up the data from the scope variable, but it didn’t work.

It was a timing problem. Both the view.postScript method and the button event handler’s onComplete events both must have started running before the partial refresh was done, because the data was not available to the XAgent. I even updated it to use XSP.partialRefreshPost to try to push the data to the scope variable and execute the XAgent page load with an oncomplete callback, but that still didn’t fix the timing problem.

If I put that code on a separate button and manually clicked it, then that provided enough time for the partial refresh to get the data into the scope variable. But that wasn’t good enough. I wanted this to be a one-click process.

Attempt 6

My next idea was to move past the dependency upon a partial refresh and just use SSJS to put the data directly into a scope variable.

Client JS to put data into a text field (NOT BOUND, BUT MUST BE AN XPAGE CONTROL)

Server JS on button to take data from component and put in scope var immediately

Client JS on complete to open XAgent page

However, the timing was still problematic. It just didn’t update fast enough for the data to be available to SSJS when the CSJS had put it in the field immediately prior. If I click the button a second time, it works fine, but it’s probably picking up the previously-stored data.

Attempt 7

I tried a few different methods (client JS and SSJS) to add delays and timers to the process (wrapped in loops to pause and then check), but none of it worked.

Attempt 8

Tweaking attempt 6 a bit, I decided to split steps 2 and 3 out to a separate hidden button and have the first button trigger the second button to execute.

The timing issue was still apparent.

The Solution

Ultimately, this is why the alert box is there in step 1. Waiting for the user to click it provides the required delay for the data to be available to SSJS.

It’s mildly annoying, but the process works.

However, if you have suggestions on how to improve this process, I’d love to hear them!


Dojo Data Grid – Part 17: EnhancedGrid Drag and Drop

$
0
0

The DnD (Drag and Drop) plugin for the Dojo EnhancedGrid gives you the ability to rearrange grid columns and rows. It also gives you the ability to drag cell contents to other cells. In this post, I’ll show how to add the plugin and use it.

Dojo Data Grid Series

This post assumes you already have a Dojo Data Grid control set up to use the Dojo EnhancedGrid, based on the instructions in this post.

1. Load the DnD Plugin

The dojox.grid.enhanced.plugins.DnD module must be included on the page, so add it to the page resources. Properties > Resources > Add > Dojo Module…
Data Grid 17 - 1 - Add Module

2. Add the Plugin to the Grid

The first step made the plugin module available, but you also need to add it to the grid.

Go to the properties of the Dojo Data Grid > Dojo and click the Add button. Add two properties as shown here:
Data Grid 17 - 2 - Add Plugin

Note: The row selection property is necessary in order to rearrange rows in the grid.

Moving Columns

To move a column, click on the column header to select the column, release the mouse button, then click in any column cell and drag it to the left or right. When you let it go, it will drop into the new location.

Along with rearranging the columns, it will refresh the grid and move you back to the top.

Before
Data Grid 17 - 3a - Before Column Move

After
Data Grid 17 - 3b - After Column Move

Moving Rows

To move a row, click on the row selector and then click in any cell in the row and drag it to a new location.

You can even select multiple rows and move them at the same time. If you select non-adjacent rows, they will all move to the new location together and will become adjacent.

Before
Data Grid 17 - 4a - Before Row Move

After
Data Grid 17 - 4b - After Row Move

Moving Cells

To move data from one cell to another. Click on the cell to select it, release the mouse button, then click on the selected cell and drag it to another cell. Once you drop it, the originating cell will be blank and the target cell will be overwritten with the data that you selected.

You can move data from multiple cells, but you cannot move data from multiple non-adjacent cells.

Before
Data Grid 17 - 5a - Before Cell Move

After
Data Grid 17 - 5b - After Cell Move

Note: If you are using a restJsonService, you cannot move cells unless your website document is set up to allow Post actions. It works fine with a restViewItemFileService either way.

IMPORTANT: It saves the changes automatically when you move cells! (When you move a row or column, it does not alter the original data.)

More Options

You can create a configuration object for the plugin to limit the options available (eg prevent row, column, or cell movement.)

You can even configure the plugin to allow you to drag data out of the grid and into another grid.

See the plugin documentation for more information.


Dojo Data Grid – Part 18: EnhancedGrid Context Menus

$
0
0

The Menu plugin for the Dojo EnhancedGrid gives you the ability to add context menus to grid headers, rows, cells, and selected regions. In this post, I’ll show how to add the plugin and use it.

Dojo Data Grid Series

This post assumes you already have a Dojo Data Grid control set up to use the Dojo EnhancedGrid, based on the instructions in this post.

Load the Menu Plugin (and more)

The dojox.grid.enhanced.plugins.Menu module must be included on the page, so add it to the page resources. Properties > Resources > Add > Dojo Module…

Dojo Grid 18 - 1 - Modules

You also need to include the dijit menu and dijit menu item modules, since they’re required to build the context menus.

In this grid, I’m also including the Printer plugin, so I can trigger printing functions from the context menus.

Add the Plugin to the Grid

The first step made the plugin module available, but you also need to add it to the grid.

Go to the properties of the Dojo Data Grid > Dojo and click the Add button. Add two properties as shown here:

Dojo Grid 18 - 2 - Dojo Properties

This is the first example of including more than one EnhancedGrid plugin.

contextMenus is the name of a JavaScript object I’ve created to build the context menus (shown below).

Note: The row selection property is necessary in order to select rows in the grid.

Creating the Menus

To create the context menus, you create an object that contains properties for all of the context menus to define. You then add menu items to those menus and provide onClick events to take action when a context menu option is selected. Example code is shown below.

Using the Context Menus

Right click on column header
Dojo Grid 18 - 3 - Cell Header Context Menu

Right click on row selector
Dojo Grid 18 - 4 - Row Context Menu

Right click on a cell
Dojo Grid 18 - 5 - Cell Context Menu

Right click on a selected region
Dojo Grid 18 - 6 - Selected Region Context Menu

Context Menu Code

This code builds all four types of context menus and provides several actions:

// Set up the context menu object dijit menus
var contextMenus = {
  headerMenu: new dijit.Menu(),
  rowMenu: new dijit.Menu(),
  cellMenu: new dijit.Menu(),
  selectedRegionMenu: new dijit.Menu()
};

// Header Context Menu 
contextMenus.headerMenu.addChild(new dijit.MenuItem({label: "Print All", onClick:printAll}));
contextMenus.headerMenu.addChild(new dijit.MenuItem({label: "Print Selected", onClick:printSelected}));
contextMenus.headerMenu.addChild(new dijit.MenuItem({label: "Print Custom", onClick:printCustomized}));
contextMenus.headerMenu.startup();

// Row Context Menu
contextMenus.rowMenu.addChild(new dijit.MenuItem({label: "Display Click Location", onClick: rowDisplayClickLocation}));
contextMenus.rowMenu.addChild(new dijit.MenuItem({label: "Preview All", onClick:previewAll}));
contextMenus.rowMenu.addChild(new dijit.MenuItem({label: "Preview Selected", onClick:previewSelected}));
contextMenus.rowMenu.addChild(new dijit.MenuItem({label: "Preview Custom", onClick:previewCustomized}));
contextMenus.rowMenu.startup();

// Cell Context Menu
contextMenus.cellMenu.addChild(new dijit.MenuItem({label: "Display Click Location", onClick:cellDisplayClickLocation}));
contextMenus.cellMenu.addChild(new dijit.MenuItem({label: "Cell Menu Item 1", iconClass:'dijitEditorIcon dijitEditorIconCopy',onClick:function(){alert('copy!')}}));
contextMenus.cellMenu.addChild(new dijit.MenuItem({label: "Cell Menu Item 2", iconClass:'dijitEditorIcon dijitEditorIconPaste', onClick:function(){alert('paste!')}}));
contextMenus.cellMenu.addChild(new dijit.MenuItem({label: "Cell Menu Item 3", iconClass:'dijitEditorIcon dijitEditorIconCut', onClick:function(){alert('cut!')}}));
contextMenus.cellMenu.startup();

// Selected Region Context Menu
contextMenus.selectedRegionMenu.addChild(new dijit.MenuItem({label: "Context Info", onClick:function(){alert('row: ' + rowIndex + '\ncell: ' + cellIndex);}}));
contextMenus.selectedRegionMenu.addChild(new dijit.MenuItem({label: "Alert", onClick:function(){alert('selected region')}}));
contextMenus.selectedRegionMenu.startup();

Note: There are references to print and print preview functions from the Printer plugin for the EnhancedGrid. The code for those functions can be found in this post.

Working with the Click Context

What we’ve seen so far is fine to trigger javascript events, but, commonly, you will want to know the context of the click so you can execute logic targeted to that context.

At least 1 of 4 events is fired on the grid when the user right clicks to bring up a context menu:

  • onRowContextMenu(e)
  • onCellContextMenu(e)
  • onHeaderCellContextMenu(e)
  • onSelectedRegionContextMenu(e)

These event handlers all automatically get a handle to an event object (e) that has context information.

To use them, you need to attach a function to the event. In that function, you can set global JavaScript variables with context information. Then, in your context menu item functions, you can access that context information.

To retrieve and use the location of a context menu click, follow these steps:

1. Define global variables and the event handler function(s) in an Output Script tag or client-side JavaScript library:

var rowIndex;
var cellIndex;
		
// onRowContextMenu Event Handler
// Retrieves the rowIndex and cellIndex and stores them in a global variable for the menu click event handlers to reference
// NOTE: This event fires when clicking on a row selector or any cell in the row
function rowContextMenuEvent (e) {
  rowIndex = e.rowIndex;
  cellIndex = e.cellIndex;
}

// onCellContextMenu Event Handler
// Retrieves the rowIndex and cellIndex and stores them in a global variable for the menu click event handlers to reference
// NOTE: This event fires when clicking on any cell in the row. If an onRowContextMenu event handler is also defined, that will fire before this event handler fires.
function cellContextMenuEvent (e) {
  rowIndex = e.rowIndex;
  cellIndex = e.cellIndex;
}

2. Attach the event handler functions to the grid events on the onClientLoad event of your page. Here are examples of attaching to two of the events:

dojo.connect(dijit.byId("#{id:djxDataGrid1}"), "onRowContextMenu", rowContextMenuEvent);
dojo.connect(dijit.byId("#{id:djxDataGrid1}"), "onCellContextMenu", cellContextMenuEvent);

3. Create one or more menu action functions that refer to the global variables and then work with the context.

// Row context menu function to display the index of the row
function rowDisplayClickLocation () {
  alert('row index: ' + rowIndex + '\ncell index: ' + cellIndex);		
}
		
// Cell context menu function to display the index of the row and cell
function cellDisplayClickLocation () {
  alert('row index: ' + rowIndex + '\ncell index: ' + cellIndex);		
}

When the user right-clicks on a row selector to bring up the row context menu, the onRowContextMenu event function runs first, then the context menu is displayed, then the user selects an option from the context menu.

It is interesting to note that if you have event handlers defined for onRowContextMenu and onCellContextMenu, the onRowContextMenu event handler will run first, then the onCellContextMenu event handler will run second. If all you’re doing is getting the row and cell index where the click happend, you don’t even need the onCellContextMenu event handler.

Properties of the Event Object

Just for kicks, I ran a little script to tell me all of the properties available to that event object that’s provided to the context menu event handlers.

Here’s the list, in case you’d like to dig into any of them further:

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, originalTarget, explicitOriginalTarget, preventBubble, preventCapture, getPreventDefault, isTrusted, view, detail, initUIEvent, layerX, layerY, cancelBubble, NONE, CAPTURING_PHASE, AT_TARGET, BUBBLING_PHASE, MOUSEDOWN, MOUSEUP, MOUSEOVER, MOUSEOUT, MOUSEMOVE, MOUSEDRAG, CLICK, DBLCLICK, KEYDOWN, KEYUP, KEYPRESS, DRAGDROP, 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


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.


Categorized Dojo TreeGrid in XPages – Additional Features

$
0
0

In the last post I showed how to create a categorized Dojo TreeGrid. In this post, we’ll take a look at a few extra features available to the grid.

The code from the last post serves as the baseline and this post will highlight any changes that are required.

defaultOpen

The defaultOpen property can be added to define whether the grid should be expanded when rendered.

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

TreeGrid_2_a_DefaultOpen

expandoCell

The expandoCell property defines which cell should include the expand/collapse icon. It’s a 0-based index, so to put the icon in the second column, give it a value of 1.

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

TreeGrid_2_b_ExpandoCell

Up Next

In the next post, I’ll show how to add counts and totals to the TreeGrid.


Viewing all 216 articles
Browse latest View live