Nov 21st – DCLUG – Managing large data sets in an XPages application

Next week I have the privilege of speaking at DCLUG again. This time discussing EXTJS and how to manage large data sets in an XPages application.

For more information and to sign up for the meeting please go here: http://www.meetup.com/DC-Lotus-Professionals/events/150317872/

Date

November 21st

Time

11:00-11:30 Networking/Lunch

11:30-12:30 Presentation

12:30-13:00 Networking/Close

Abstract

Moving notes client views to the web has always been a challenge. Out of the box XPages provides us the viewPanels and Dojo Data Grids which do a decent job of getting the data to a user, but it not an optimal user experience. Clicking next 25 paging through 3000 documents is not ideal.

With large data set management built in and user customizable features like REST service integration, categorization, sorting, searching, column totals, filtering, charting and many more, EXTJS grids give us the ability to put the power of instant data management into a web user’s hands.

Mark will demonstrate and provide examples of how the EXTJS library can be used to effectively meet a requirement to modernize large notes client views (1000s of documents at the same time).

 

Lunch will be provided for attendees by IBM

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.

EXTJS in XPages #11 – Grids with Locked Column(s)

In this article I will highlight a grid column property which allows the developer to lock the columns on an EXTJS grid in a similar fashion to freezing a frame in excel.

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/xGridLocked.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

Locking a column (or two or three) is a very useful way of retaining reference information on the left and being able to scroll across multiple pieces of information to the right.

Doing so in an EXTJS grid creates the effect show below whereby a horizontal scrollbar is added to the grid to the right of the locked column.

lock1

How does it work?

It is a column property ‘locked = true’. The example code below uses the REST example as demonstrated before and I added the locked property to the first column. You do not have to use the REST service example, that was the just the one I chose.

    var grid = Ext.create('Ext.grid.Panel', {
        renderTo: 'gridHere',
        frame: true,
        features: [filters],
        height: 400,
        title: 'Users',
        store: store,
        iconCls: 'icon-user',
        columns: [{
            header: 'First123',
            sortable: true,
            dataIndex: 'firstname',
            filterable: true
        }, {
            text: 'Last',
            sortable: true,
            dataIndex: 'lastname',
            locked: true,         //this is the new property
            field: {
                xtype: 'textfield'
            },
            filterable: true
        }, {.....

That’s all you need to do. the caveat is that you must have at least one scrolling column on the right – which makes sense because if they were all locked – then there would not be a need to have any locked 🙂

Interesting side note – you will notice that I locked the Last Name column which is not the first column listed in the view – but it moved to the left. You cannot lock columns 1, 3, 5 and have everything scroll around them, locked columns move to the left. I would guess there could be a performance issue in not putting them into the grid in the right order so I would suggest you always list your locked columns first 🙂

EXTJS in XPages #10 – Grid Row Expander plugin

In this article I will demonstrate how to add expandable and collapsible sections to each grid row using the extjs rowexpander plugin.

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/xGridExpand.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

Now we have the basics of how to build a grid down I am going to demonstrate some of the cool plugins and features which can be added to the EXTJS grids within our XPages. This article focus on the “rowexpander” plugin which gives us the ability to “expand the row” and format the displayed output.

In the example I have created I have used a normal grid showing 240+ countries and territories in the world. When you click on the row it expands and show the reformatted data fields and the country’s flag.

row1row2

How does it work?

There are no new code bases to be loaded for this plugin it is “out of the box” so to speak. It really could not be simpler…..

I created a new view “vwCountry” and a new REST service to pump out the data

<xe:restService id="country" pathInfo="country">
	<xe:this.service>
		<xe:viewJsonService defaultColumns="true" viewName="vwCountry" count="10000"></xe:viewJsonService>
	</xe:this.service>
</xe:restService>

And then the grid code to retrieve it is the same as the the bufferedRendered example (with new field names)

The significant part is adding the plugin to the grid and inserting the rendering code – but that is also copy and pastable almost. As you can see from the code below there is a ptype: rowexpander plugin and then within that I use an EXT.XTemplate to show the code which I want to display on the screen while the row is expanded. It is as simple as that.

    var grid = Ext.create('Ext.grid.Panel', {
        renderTo: 'gridHere',
        frame: true,
        height: 400,
        layout: 'fit',
        title: 'Countries',
        plugins: [{
            ptype: 'rowexpander',
            rowBodyTpl : new Ext.XTemplate(
                '<p><b>Country:</b> {Name}</p>',
                '<p><b>Life Expectancy:</b> {Life}</p><br>',
                '<p><b>Highest Peak:</b> {HighestPeak}</p>',
                '<p><img class="flag" src="http://www.worldatlas.com/webimage/flags/countrys/zzzflags/{CountryCode:this.lowerCase}large.gif"/></p>',
            {
            	lowerCase: function(cc){
            		return cc.toLowerCase()
            	}
            })
        }],

Conclusion
Once again this shows how quick and simple it is to provide excellent functionality for your users with little to no effort on your part as the developer – and that is really what we all like 🙂

Side note
Originally I used the buffereredRenderer on this grid but there appears to be a display issue (v4.2) which has been reported and tracked with Sencha. I removed the bufferedRenderer and it worked fine.

EXTJS in XPages #9 – Infinite scrolling rebooted and reborn – v4.2 BufferedRenderer

In this article I will introduce the new EXTJS v4.2 Infinite scroller – the BuffererRenderer. The whole concept of infinite scrolling has been rewritten in the new version of the grid and it had made a huge difference to responsiveness and stability of the infinite grid.

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/xBufferedRenderer.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

The first thing I suggest you do is go and watch this blog post and video – it explains in WAY more detail that I possibly could how cool the new grid is.

Blog: First Look at Ext JS 4.2 Grid

Video: http://vimeo.com/59611158

And for those of you who couldn’t be bothered the upshot of all of this is that the grids are WAY better than ion 4.2. The synopsis from my perspective is:

  • 10,000 records are loaded into memory and only a fraction of them are displayed in the grid. As soon as a new row is scrolled into view and added to the grid’s DOM model – a previous row is removed. This makes for a small DOM and takes up a finite amount of browser memory.
    • The increase in XPiNC responsiveness is night and day – it is that much better
  • We no longer need remote filtering and sorting
    • thank goodness cos that was a pain and not as flexible as I wanted it to be anyway
  • We can easily take advantage of grouping a large number of records efficiently
  • Selecting multiple documents across the infinite scroller is retained (HUGE DEAL)
  • Grouping is seamless across the infinite scrolling
  • The only downside is that you have to wait for 10,000 records to load from the REST service – and that can be optimized as I have previous discussed
    • This also means we need to be smarter in reloading the grid only when we really need to
    • If it is a 100k download and takes 10 seconds to render – the user will wait the first time but not every time they need to open a document and then come back to the grid because it was not the one they wanted.
  • In some ways Buffered Render is not really an infinite scroller – because it does not go back to the server ever time the user needs to scroll down – it is really just a highly efficient document display modeler I guess.

So how do we make it work

Well there are some changes to our code but not too many and all of these should be familiar concepts based on the rest of the posts in this series so far.

First  we need to load the data into memory – we do this by calling a REST service. We are going to use a different REST service as well. In the previous infinite scroll example we needed the REST service to tell us how many documents there were in the remote store – we do not need that any more as we are going  to load every document in the view. We are going to use a flat JSON ViewService to pull all the documents from the ByFirstName view

The new REST Service

<xe:restService id="byFirstNameFlat" pathInfo="byFirstNameFlat">
	<xe:this.service>
		<xe:viewJsonService defaultColumns="true" count="10000&" viewName="byFirstName">
		</xe:viewJsonService>
	</xe:this.service>
</xe:restService>

Starting the process
The data is loaded in a different way for the bufferedRenderer. Instead of being loaded from a remote service it is loaded from a local data model. That is not a problem for us but means an extra step in the data management process.

The process kicks off in the following manner

Ext.onReady(function(){

	//We are going to pass to the getDataGrid function the url we want
	//and the callback function which we then wants executing once the data is retrieved
	//doing it this way gives us maximum control over what happens to the data
	//and makes the function more generic should we want to use it for other data
	//and other grids in the future

	var url = 'xRestService.xsp/byFirstNameFlat?count=100000',
			theData
	var theCallBack = function(theData){
			createGrid(theData)
			}
	getGridData(url, theCallBack)
});

In this function we “getGridData” and then passing in a callback function we determine what happens after the data is loaded. We are passing in the function variable theCallBack. This allows the getGridData fucntion to execute on what was passed in to it.

I cannot streaa enough how much of a good JavaScript coding practice this is. Instead of hard coding the getGridData function to call the next step in the process – we tell it what to do.
This creates a generic getGridData function which does nothing but that. It can then be used to do anything else we want – because we are able to tell it what to do next.

Check out my article on Make your XPages more maintainable – JavaScript Callback functions for a better understanding on how callback functions work.

function aRandomNumber() {
	return Math.floor(Math.random()*1000001)
}

function getGridData(url, callback){

	var dataURL = url
	$.ajax({
		  url: dataURL+&quot;&amp;rand=&quot;+aRandomNumber()+&quot;&amp;&quot;
		}).done(function ( data ) {
			//one the data is loaded then pass it to the callback function
			//passed in as one of the function arguments
			if(callback){
				callback(data)
			}
		});
}

Loading the data

$.ajax({
	url: 'xRestService.xsp/byFirstNameFlat'
	})
	.done(function ( data ) {
		createGrid2(data)
	});

The grid code has an additional feature for bufferedrenderer. This can either be added as a text string property in the grid or as a feature object of type bufferrenderer. If we do it this second way we have more control over its properties and it separates it from the main grid code.

buffRend = new Ext.grid.plugin.BufferedRenderer( {
        pluginId: 'buff1'
})

The grid
The grid has the renderer as a grid property.

&lt;strong&gt;The store&lt;/strong&gt;
	store = new Ext.data.Store({
		model : 'Person',
		autoLoad: true,
		sorters: {
			        property : 'firstName',
			        direction: 'ASC'
			    },
		id: 'store',
		data: data,
		remoteFilter: false,
		buffered: false,
        proxy: {
            type: 'memory'
        }
	});

The Grid
The grid itself is not that different from the previous examples except for the one critical line – plugins: buffRend

The BufferedRendered is added as a plugin by first defining the Ext.grid.plugin.BufferedRenderer and then adding it as a property to the grid during creation.

 var filters = {
        ftype: 'filters',
        // encode and local configuration options defined previously for easier reuse
        //encode: encode, // json encode the filter query
        local: local   // defaults to false (remote filtering)

	};

buffRend = new Ext.grid.plugin.BufferedRenderer( {
     pluginId: 'buff1'
})

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

    var grid = Ext.create('Ext.grid.Panel', {
        renderTo: 'gridHere',
        frame: true,
        height: 400,
        title: 'Users',
	plugins: buffRend,
        features: [filters],
        store: store,
        iconCls: 'icon-user',
        columns: [{
            header: 'First',
            sortable: true,
            dataIndex: 'firstname',
            filterable: true
        }, {
            text: 'Last',
            sortable: true,
            dataIndex: 'lastname',
            field: {
                xtype: 'textfield'
            },
            filterable: true
        }, {
            text: 'Address',
            sortable: true,
            dataIndex: 'address',
            field: {
                xtype: 'textfield'
            },
            filterable: true
        }, {
            text: 'City',
            sortable: true,
            dataIndex: 'city',
            field: {
                xtype: 'textfield'
            },
            filterable: true
        },{
            text: 'State',
            width: 80,
            sortable: true,
            dataIndex: 'state',
            field: {
                xtype: 'textfield'
            },
            filterable: true
        }],
        // paging bar on the bottom
        bbar: gridDock
    });

}

Performance
If you watched the video you will see how the improvements in performance are handled. In the example shown in the video they show that for a few thousand records there are hundreds of thousands of DOM elements created for an old flat grid to be scrolled through. Using the new bufferedRenderer there are always the same number loaded – more in the order of 2,000 regardless of the overall data size. This is acheived by programmatically adding an removing the table rows to the visible grid as the user scrolls up and down the grid. If the user can see 25 rows then only 27 rows are loaded. The grid Tbale displaying the data is constantly modified as the user scrolls up and down.

The difference between 4.1.1 and 4.2 is night and day. I don’t just say that lightly. In the latest application we were working on, with 4.1.1 we were seeing a slowdown in XPiNC around 200 documents in the view. With 4.2 we have loaded over 3000 documents without any significant reduction in responsivness and/or speed of the grid.

Night and Day – it is a different application to interact with – quite remarkable!!

Conclusion
Looking at this demo alone on a modern browser you would not really notice much of a difference – but if you compare some of the other examples in XPiNC to this one you will really notice the improvement. That larger the data set the better then improvement.