1291: EXTJS in XPages: Modernizing IBM Notes Views Without Sacrificing Functionality

I am very happy to announce that I have been accepted to speak a second time at IBM Connect this year !!

In this session I am going to quickly review some of the previously published EXTJS related items on the blog (apparently not everyone reads it) but more importantly I want to discuss user experience. If nothing else, I have learned over the last 18 months that not only is EXTJS very cool in loading thousands of notes documents into  usable and functional web based grids, it also demands a serious serious respect and insight into the user experience. If your user’s are willing to wait many seconds to load thousands of documents they will not be willing to load thousands of documents again, and again, and again, as they go to view each individual document and go “uuurgh, not that one” and then reload the web page.

But this is XPages, and we have the technology to do better with a little forethought and planning. There is a monumental difference between:

1. Being able to meet a requirement to load thousands of documents into a grid and allow the user to open and review each document

and

2. Making them not hate the application in meeting requirement 1

(By the way – requirement 2 is never written in the same document as requirement 1 – it is kinda assumed)

 

Come and see the session, I promise it will be entertaining, there will be lots of demonstrations, stacks of free code and you will be able to relate to issues and requirements which you will have come across when modernizing IBM Notes applications.

 

The Abstract

In this session Mark will demonstrate how the use of the EXTJS library can effectively meet the requirement to modernize large data applications without sacrificing user interface familiarity. EXTJS is the only grid framework with large data set optimization built in.
Mark will discuss and provide working examples of how to manage large data sets and with it an exceptional user experience. You will learn how to display 1000s of  documents through XPages in ways previously considered impossible. With user customizable features like REST service integration, categorization, sorting, searching, column totals, filtering, charting, exporting to excel and many more you can put the power of instant data management back into a web user’s hands.
Advertisements

EXTJS in XPages #17 – Manually Updating row data via REST

In this article I will demonstrate how we can use the same REST service we used to populate our grid, to accept an update to one of the row values.

EXTJS in XPages series
Here are links to all of the previous articles in this series

Demonstration
The EXTJS in XPages demonstration database can be found at http://demo.xomino.com/xomino/Extjs.nsf/xContextMenuRESTUpdate.xsp

Download
The sample database that will accompany this series is available from the menu in the live demo database from this link – http://demo.xomino.com/xomino/Extjs.nsf

Introduction

In the last article we saw how we can easily create a context menu and in this article we are going to add the icing on the user experience – updating the data on the server via a REST service. Once again in this incremental series, this is a small mount of additional effort to effect a large benefit for the end user experience.

r1

How does that work?

In the XPages world we are able to update a document via a REST service using the URL format:

  • /path/database.nsf/rest.xsp/restPathInfo/UNID

To that URL we have to post (or in this case PUT) a JSON string in the format {“field”: “value”, “field2”: “value2”}

To achieve this we have to “handle” the context menu in the following manner:

var approveAction = Ext.create('Ext.Action', {
    icon   : 'images/add.png',  // Use a URL in the icon config
    text: 'Approve Users',
    disabled: false,
    handler: function(widget, event) {
        var selection = Ext.getCmp('myPanel').getSelectionModel().getSelection()[0];
        buildSubmit.updateREST(selection, "Approved")

    }
})

In this handler we get the selected document, and pass that “selection” and the status value to be updated to the buildSubmit.updateREST function.

Within the function we:

  • Build the JSON data string
    • {‘unid’: selection.data[“@unid”], ‘status’: status}
    • The @UNID is pulled from the REST service property of this document
    • The status is passed into the function “Approved”
  • Pass the string array to the updateStatus function which in turn
    • Creates an AJAX “PUT” request to the REST service
      • xRestService.xsp/byFirstNameFlat/’+data.unid
    • If this successfully updates we then update the row field value with the “Approved value”
var buildSubmit = {

	updateREST: function(selection, status){
		var data = {'unid': selection.data["@unid"],
					'status': status}
		buildSubmit.updateStatus(selection, data)
	},
	updateStatus: function(selection, data) {
		  $.ajax({
			   type: 'PUT',
			   contentType: 'application/json',
			   url: 'xRestService.xsp/byFirstNameFlat/'+data.unid,
			   dataType: "json",
			   data: JSON.stringify(data),
			   success: function(response, textStatus, jqXHR){
				  //update the cell in the grid
				  selection.set("status",data.status);
				},
				error: function(jqXHR, textStatus, errorThrown){
					   console.log('sort order error: ' + textStatus);
				}
		  	});
	}
}

When the data is successfully updated in the grid the EXTJS code automatically adds a marker which indicates that the contents of this field have been changed

r2

Looking at this through Firebug

As we can see from the image below – the “PUT” request is sent to the URL
http://copper.xomino.com/xomino/Extjs.nsf/xRestService.xsp/byFirstNameFlat/B73326B7CDBEFAD385257B1B00254C58
and the JSON is successfully sent to the REST service.

r3

 

Conclusion

There are a few of things to think about in this example:

  1. Once again a few simple lines of code provides a huge benefit to the user.
    1. Being able to update data directly from the grid means they can get their job done faster
    2. Being able to update via a right click action menu means they can get their job done faster
  2. Being able to update the grid field values programmatically means we do not have to reload the grid data
    1. If this is the only point you take away form the this article this is the most important.
    2. Reloading grid data has a cost, in data consumption on the server, time expended to download, time to redraw and most importantly time wasted for the user getting a confirmation that their data was updated. If you have a large number of data rows and the update is in the order of seconds then the user has to wait. Updating a single field of data is instantaneous and the user experience gratification is immediately satisfied.
    3. If you are updating data within the grid itself – DO NOT reload the grid data from the server – EVER – plan your user experience better !
  3. Using the jsonviewservice out of the box EXTLib REST service allows me to make field updates to the database with a tint data footprint.
  4. I cannot understate how awesome #3 is !

EXTJS in XPages #16 – Right Click Context Menus

In this article I will discuss and demonstrate how to create a context menu so that when a user right clicks on  grid they are able to take action.

EXTJS in XPages series
Here are links to all of the previous articles in this series

Demonstration
The EXTJS in XPages demonstration database can be found at http://demo.xomino.com/xomino/Extjs.nsf/xBufferedRendererContextMenu.xsp

Download
The sample database that will accompany this series is available from the menu in the live demo database from this link – http://demo.xomino.com/xomino/Extjs.nsf

Introduction

Context menus (right click menu) are one form of user experience which “reduces clicks”. But that is a fallacy in some senses because the user is not clicking any less they are just less annoyed by the clicks 🙂

If the user is in a grid and has to select some documents, then move their mouse out of the grid and click an “Approve” button that is “one click” but “with an annoying mouse movement”. Right click – select Approve is actually two clicks…..which is more clicks but is less annoying because of little to no mouse movement.

r1

How does that work?

In the grid we are able to create a context menu by “Creating a menu” and “adding it to the onContextMenu event of the grid view. In the example below I am creating the “approveAction”. In that action we:

  • define the icon which will appear int he menu
  • define the text on the menu action
  • have a handler which interprets what happens when the menu action is clicked
    • In this case the action gets all the selected documents and creates a JSON string from it
    • That JSON string is then shown on the console for debugging
var approveAction = Ext.create('Ext.Action', {
    icon   : 'images/add.png',  // Use a URL in the icon config
    text: 'Approve Users',
    disabled: false,
    handler: function(widget, event) {
        var selection = Ext.getCmp('myPanel').getSelectionModel().getSelection();

    	var theJSON=[]
    	for ( var i = 0; i < selection.length; i++) {
    		theJSON.push({'unid': selection[i].data["@unid"],
    						'field': 'status',
    						'val': 'approved'}
    		)
    	}
    	console.dir(theJSON)
    }
})

I will get to writing about how to do the update like I promised……but suffice to say that this is posted at a customREST control which will then update the values based on the UNID.

r2

The menu is added to the grid in the following manner. A view listener is added to the grid and in the itemcontextmenu event we detect where the event is happening and then display the menu at exactly that point (you could offset this if you wanted)

The menu is smart enough that if you do not click on the menu itself it disappears when you click away from it.

gridFunc = {
    self: this,
    grid: function(){
	  return Ext.getCmp('myPanel')
    },
    contextMenu: Ext.create('Ext.menu.Menu', {
        items: [
                approveAction,
                rejectAction
        ]
    })
}

var grid = Ext.create('Ext.grid.Panel', {
    	viewConfig: {
    		stripeRows: true,
	        listeners: {
	            itemcontextmenu: function(view, rec, node, index, e) {
	                e.stopEvent();
	                gridFunc.contextMenu.showAt(e.getXY());
	                return false;
	            }
	        }
	    },
etc

Reusing the actions

Not only can the actions be added to the context menu, but because they are created as EXT Objects they can be added to other place as well – like a toolbar

	gridDock = [{
		id: 'activeStorePaging',
	     xtype: 'toolbar',
	     dock: 'bottom',
		 items: [
		   {
		       text: 'Clear Filter Data',
		       handler: function () {
		           grid.filters.clearFilters();
		       }
		   },
		   '-',
		   approveAction,
		   '-',
		   rejectAction
	    ]
	 }]

r3

Conclusion

As you can see with great control and little additional effort (as ever!) we are able to add more feature rich functionality to our grid

EXTJS in XPages #15 – Multi-select of documents

In this article I will discuss and highlight “multiselect” within the extjs grid. With a two line addition to the grid code we are able to select multiple documents and then action them.

EXTJS in XPages series
Here are links to all of the previous articles in this series

Demonstration
The EXTJS in XPages demonstration database can be found at http://demo.xomino.com/xomino/Extjs.nsf/xBufferedRendererCheckBox.xsp

Download
The sample database that will accompany this series is available from the menu in the live demo database from this link – http://demo.xomino.com/xomino/Extjs.nsf

Introduction

To be able to action more than one document we want to be able to select more than one in the grid. There are two ways we can do this and we are going to look at both. In the next article we will look at how to affect change to multiple documents.

mul1

How does that work?

Within the grid configurations we are able to add a multiSelect  parameter which allows us to select multiple documents. This can be achieved in the same way as you would multiselect in excel:

  • Hold down the SHIFT key and click two documents, Everything inbetween will be selected as well as the two documents clicked
  • Hold down the CTRL key and the documents clicked will be selected in addition to each other
  • Single clicking any other document without either key held down will only select the document clicked

mul2

  var grid = Ext.create('Ext.grid.Panel', {
        renderTo: 'gridHere',
        frame: true,
        multiSelect: true,     // <-----------
        height: 400,
        title: 'Users',
	    plugins: buffRend,

As you can see from the above picture the multiple selected documents are all highlighted.

Going one step further (if the users prefer it) you can add a “checkboxmodel” parameter to add “checkboxes” to the grid

  var grid = Ext.create('Ext.grid.Panel', {
        renderTo: 'gridHere',
        frame: true,
        multiSelect: true,     // <-----------
        selType: 'checkboxmodel',  //<----------------
        height: 400,
        title: 'Users',
	    plugins: buffRend,

Not only does this give a new interface it also allows users to select and deselect all by clicking on the checkbox at the top of the grid

mul3

Conclusion

Adding multi-select support to the grid is very simple and in the next article we will look at how to take action on that

EXTJS in XPages #14 – Grid editing and saving data via REST CRUD

In this article I will demonstrate the ability to edit data within and EXTJS grid and have the changes saved directly to the domino database via the same REST service from whence they came.

I have to say a big thank you to Steve Zavocki who actually found my old failed attempt to make this work in my demo database. It was unblogged because I couldn’t get it to work – well that was in April, he asked some questions about it and I came back to it and with another 4 months of EXTJS experience under my belt I figured it out in no time. Without the prompting it might have sat there for another 4 months untouched.

EXTJS in XPages series
Here are links to all of the previous articles in this series

Demonstration
The EXTJS in XPages demonstration database can be found at http://demo.xomino.com/xomino/Extjs.nsf/xRESTCRUD.xsp

Download
The sample database that will accompany this series is available from the menu in the live demo database from this link – http://demo.xomino.com/xomino/Extjs.nsf

Introduction
This is really where everything starts to come together and we begin to be able to create functionality customers have always been asking for. Being able to update the data in a view without having to:

  • go to the view
  • go into a document
  • edit it
  • change it
  • save it
  • go back to the view

The Row Editor plugin
The row editor plugin is very easy to add to your grid – you create the variable for the rowEditor (taken from the website example)

 var rowEditing = Ext.create('Ext.grid.plugin.RowEditing', {
 listeners: {
	cancelEdit: function(rowEditing, context) {
	// Canceling editing of a locally added, unsaved record: remove it
	 if (context.record.phantom) {
		store.remove(context.record);
	 }
	}
 }
 });

and you add it to the grid in the same way as other plugins

	var grid = Ext.create('Ext.grid.Panel', {
	renderTo: 'gridHere',
	plugins: [rowEditing], // <----------
	height: 400,
	frame: true,
	etc.....

and as simple as that you can now double click on a grid and edit the columns!

rc1

Making it communicate with the REST service

In this example I am using a store with a proxy type of REST. The EXTJS grid already understands this is a REST service and does most of the heavy lifting by itself. For more information on how a REST service works in Domino then check out Chris Toohey’s article 

There is also more documentation on the XPages REST here http://www.openntf.org/blogs/openntf.nsf/d6plinks/NHEF-93YBTB

var store = Ext.create('Ext.data.Store', {
        autoLoad: true,
        autoSync: true,
        model: 'Person',
        proxy: {
    		pageParam: 'p', // <---important to add
            type: 'rest',      // <---- type of proxy is REST
            url: 'xRestService.xsp/byFirstNameFlat2',
            reader: {
                type: 'json',
                root: 'data'
            },
            writer: {
                type: 'json'  // <- data is written back in JSON format
            }
        }
    });

As you can see it also defines the write format back to the REST service. When you click on the UPDATE button on the row editor a URL is generated and data is sent. It does this via a “PUT” request which only sends a small amount of data to the server.

rc2

Enabling a PUT request

By default PUT is not enabled on a Domino server and you can enable it by turning on domino data services on the server or by adding it to the allowed HTTP methods on the web site document in the Address Book

For more information on that check out this blog article by Michael Brownlee

Domino REST service

The REST service URL which generates the data looks like this

http://demo.xomino.com/xomino/Extjs.nsf/xRestService.xsp/byFirstNameFlat2?_dc=1375392340494&p=1&start=0&limit=25

The REST service POST look like this

http://demo.xomino.com/xomino/Extjs.nsf/xRestService.xsp/byFirstNameFlat2/66F3A5FE2B2A38CC85257B1B00254E6D?_dc=1375392353545

As you can see the UNID has been added to the URL after the byFirstNameFlat2/
The _dc= is a random number generated by the grid

It is important to note that the grid automatically adds a “page=1” parameter to the REST service call and for some reason I cannot fathom this makes the REST service fail!

byFirstNameFlat2?_dc=1375392340494&page=1&start=0&limit=25 —-> FAIL

so I added the pageParam to the store proxy config and told it to use “p” instead – “p=1” apparently means nothing to a REST service and we all carry on happily….

and the UNID comes from where exactly?

So the other thing the grid does is by default it assumes that you want to send back the “id” field to the REST service and appends the “id” field onto the end of it

byFirstNameFlat2

becomes

byFirstNameFlat2/66F3A5FE2B2A38CC85257B1B00254E6D

To do this I added the UNID as a field in the REST service and added that as a hidden column in the grid.

Ext.define('Person', {
    extend: 'Ext.data.Model',
    fields: ['firstname', 'lastname', 'address', 'city', 'state', 'id']
});

 

        columns: [{
            header: 'id',
            sortable: true,
            dataIndex: 'id',
            hidden: true
        }, {

“id” is not the only field name which can be used as there is another proxy parameter called “idParam” similar to the pageParam which will allow you to name the “unid” field however you like – but the grid must know which field in the grid is the UNID and which it should throw back at the REST service

And that is pretty much it – you can download the sample database and take a look at the code for yourself.

If you want to get really fancy (and we will in the next blog article) you can also edit numbers/date fields and add validation as well to ensure that your users and not submitting rubbish back to the REST.

If you want to be uber secure as well there is always the querySaveDocument event on the REST service which you could also use to validate the incoming data.

rc3

Conclusion

In this article we have seen how we can easily edit data directly in the data grid and from that we can update our domino data with a tiny http post back to the server. This is good for your network traffic and fantastic for your user experience.

No XPage controls were harmed in the creation of this demonstration

EXTJS in XPages #13: Totaling your columns with a groupingSummary

In this article I will highlight a feature of the grouping capability – the “summary grouping”. This feature can be easily used to provide column totals on your grouped grid. We are also going to look at collapsing and expanding all the groups easily and further enhancing your grid features.

EXTJS in XPages series

Here are links to all of the previous articles in this series

Demonstration

The EXTJS in XPages demonstration database can be found at http://demo.xomino.com/xomino/Extjs.nsf/xGridGroupedSummary.xsp

Download

The sample database that will accompany this series is  available from the menu in the live demo database from this link – http://demo.xomino.com/xomino/Extjs.nsf

Introduction
As we saw in the previous article, adding “grouping” to a grid is relatively simple and with only a couple of changes we are also able to provide column totals. The example below shows the “sum” of the column for each group with a customizable label.

sum1

How does it work?

We are going to change two things from our grouping example. We are first going to change the Grouping feature to a GroupingSummary Feature. Other than that it is pretty much the same as before – I have added a couple of other optional parameters at the end so that the user cannot group the view by other columns.

	var groupingFeature = Ext.create('Ext.grid.feature.GroupingSummary',{
		id: 'group',
		ftype: 'groupingsummary',
		groupHeaderTpl: [
		  '{name:this.formatName} ({rows.length})', //Display "None" if there is a blank category
		  {
			  formatName: function(name) {
			  var sName = (name == "") ? "None ": name;
			  return sName;
		  }
        }],
        hideGroupedHeader: true,
        enableGroupingMenu: false,
        startCollapsed: false
	});

The second thing to change is adding parameters to the columns to show what kind of summary we want and what format we want it in.

In this case I have added the CVV2 column to the previous grid (cos it has numbers in it) and here is the column.

{
	text: 'C V V 2',
	width: 120,
	sortable: true,
	dataIndex: 'cvv2',
	field: {
		xtype: 'numberfield'
	},
	summaryType: 'sum',
	summaryRenderer: function(value, summaryData, dataIndex) {
		return value + ' widgets';
	},
	filterable: true
}

We have added the summaryType and summaryRenderer parameters to the columns object and as you can see it is fairly self explanatory what each option does. The summaryType field can be one of 5 options:

  • count
  • sum
  • min
  • max
  • average

The example below shows the “average” of the column for each group.

sum3

The summaryRenderer allows me to insert ” widgets” onto the end of the summary – but it could be hours, minutes years or any other label. You could even insert an icon or color the value based on status should you so chose.

Expand and Collapse
This code is equally applicable to the grouping feature in the last article. It is a simple peice of code which gets a handle on the feature (grouping or groupingSummary) and uses the 4.1 methods collapseAll() and expandAll() to do just that. The code below shows the updated gridDock object. Within that a “-” is a separator for the buttons.

var gridFunc = gridFunc || {}
gridFunc = {
	grid: function(){
	  return Ext.getCmp('myPanel')
	}
}

gridDock = [{
	id: 'activeStorePaging',
     xtype: 'toolbar',
     dock: 'bottom',
	 items: [
	   {
	       text: 'Clear Filter Data',
	       handler: function () {
	           grid.filters.clearFilters();
	       }
	   }
    ]
 },
'-',
   {
       text: 'Expand All',
       handler: function () {
           gridFunc.grid().features[0].expandAll();
       }
   },
'-',
  {
       text: 'Collapse All',
       handler: function () {
           gridFunc.grid().features[0].collapseAll();
       }
}]

In this example the gridFunc.grid() call is my way of getting a handle on the grid as discussed in this article. features[0] is the groupingFeature and that is it

sum4

Conclusion

The groupingSummary allows for a very simple and once again effective way of displaying data to your users using the EXTJS grid library.

EXTJS in XPages #12 – Counting categories with Grouped columns

In this article I will demonstrate how grouping can be added to the EXTJS grid within your XPage and how the number of rows within that group can be totaled and displayed to the user.

EXTJS in XPages series

Here are links to all of the previous articles in this series

Demonstration

The EXTJS in XPages demonstration database can be found at http://demo.xomino.com/xomino/Extjs.nsf/xGridGrouped.xsp

Download

The sample database that will accompany this series is  available from the menu in the live demo database from this link – http://demo.xomino.com/xomino/Extjs.nsf

Introduction

“Categorization” is something we are used to in the world of Domino Views and we can also create a pseudo total of the categorized columns by totaling an adjacent column. This is functional but is hardly aesthetically pleasing.

Grouping is easily achieved with an EXTJS grid and as we will see it is also dynamic and will re-total on filtering as well

Using the ‘Ext.grid.feature.Grouping’ feature we are easily able to create and manipulate a grid which looks like this (grouped by state)

tot1

How does it work?

We do not have to add much to our code to include the grouping feature. We first create a new variable inside of the createGrid() function representing the new feature.

var groupingFeature = Ext.create('Ext.grid.feature.Grouping',{
	ftype: 'groupingsummary',
	id: 'groupingFeature',
	groupHeaderTpl: [
		'{name:this.formatName} ({rows.length})', 
		{
		//This is used to display "None" if there is a blank category
		formatName: function(name) {
			var sName = (name == "") ? "None ": name;
			return sName;
		}
	}],
	startCollapsed: false
});

There are a few parameters to discuss in this code block but for the most part I think the code is self explanatory.

The groupHeaderTpl is the paramater to determine how the actual grouping
group line is displayed. In this case we are using the EXT templating technique.

  • {name: this.formatName} is the name of the column Header – it uses the formatName function to conditionally display the name of the column
  • {rows.length} is the number of rows within the group (the total)
  • formatName (referenced in the previous this.formatName) is a conditional function which determines if there is a value to even display as the group header. In this case if there are blank fields in the grouped column it will display “None” instead of just displaying nothing and a number.

After adding this new variable we have to add the feature to the grid in the features parameter:

var grid = Ext.create('Ext.grid.Panel', {
	renderTo: 'gridHere',
	frame: true,
	height: 400,
	title: 'Users',
	plugins: buffRend,
	features: [groupingFeature, filters],
	etc

Finally we need to tell the store which field to initially group using the groupField and groupDir parameters:

store = new Ext.data.Store({
	groupField: "state",
	groupDir: 'ASC',
	model : 'Person',
	autoLoad: true,
	sorters: {
				property : 'firstName',
				direction: 'ASC'
			},
	id: 'store',
	data: data,
	remoteFilter: false,
	buffered: false,
	proxy: {
		type: 'memory'
	}
});

That’s it – once again a big increase in functionality with a relatively simple incremental code addition to the grids we already had.

Some other neat things grouping does for us
When we have a grouped grid and we go to filter it the group header is dynamically re-calculated based on the filtered results

tot2

Adding the grouping feature to the grid allows us to group any of the columns by selecting the “group by this column” option which now appears in the column dropdown

tot3

tot4

As you can also see from the images above – the user has the option to remove grouping as well “Show in groups”

Conclusion
In this article I have only scratched the surface of the possibilities with the grouped grid. Good looking categories with counts are something we have strived for in Notes for a long time and I think this is a very neat solution.