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 🙂

Advertisements

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.

EXTJS in XPages #8 – Selecting data from the grid and opening a document

In this article I will show you how to get data upon selecting a document – I will then demonstrate how we open a document from the 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/xGridOpenDocument.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

Selection Model

The selection model for a grid is very easily retrieved using the dblclick event within the grid:

 var selection =  grid.getSelectionModel().getSelection();
 var fieldValue = selection[0].data.fieldname

Listeners

To detect the double click event we have to add a listener to the grid. There are many listeners for the grid and more information on them can be found here in the Sencha help files

grid = Ext.create('Ext.grid.Panel', {
	listeners: {      //listener config setting for the grid
		events HERE.....................
	}
}

Getting the UNID

In out view we are going to add a  new column docid – very simple @Text(@DocumentUniqueID) column formula. But this column is not going to be displayed in the view – it is going to be hidden.

We add the new column and add the “hidden: true” property to it in the grid so that it is not visible to the user. But this does mean that it is available to us programmatically.

{
	text: 'docid',
	width: 10,
	dataIndex: 'docid',
	hidden: true
},

(Note: the @UNID property is passed in by the REST service and *yes* we could use that instead of adding a new column – however I feel that this is easier to explain and does not require a discussion regarding the usage of a property of the REST service JSON array )

Opening the document

In our example grid we are simply going to open our document by getting the docid field from the hidden column and opening a window using a manually generated URL string

So all together or grid code now looks like this:

grid = Ext.create('Ext.grid.Panel', {
	renderTo: 'gridHere',
	frame: true,
	features: [filters],
	height: 400,
	title: 'Users',
	store: store,
	iconCls: 'icon-user',
	listeners: {      //listener config setting for the grid
		itemdblclick: function(dataview, record, item, index, e) {
			console.log('dblClick Event')
			var selection = grid.getSelectionModel().getSelection()
			window.open('xPerson.xsp?action=readDocument&documentId='+selection[0].data.docid)
		}
	}
}

As you can see from the example page this is simple, effective and completely poor as a user experience – but this article was about getting data from the grid – not about making a cool application 🙂

Conclusion

Programmatically accessing data in the grid is a very powerful technique when attempting to manipulate the EXTJS grids and insert them into your application.

EXTJS in XPages #7 – Doing an @Unique(@DbColumn) equivalent in the grid

In this article I will show you how to make a simple @Unique(@DbColumn)) equivalent in the EXTJS grid

Introduction

When you have a notes view/EXTJS grid you often want to be able to do a lookup of all the unique values in a column – this is especially useful when you are also applying filters in the grid.

What is particularly useful is that the values in the column are uniqued automatically – this allows you to reduce down the values which are presented to the user to select from. This also means not having to create some elaborate search/filter capability going back to the server to change the lookup.

Caveat

This only works on the data in the current view so will not work if you are using remote filtering. In a later article I will demonstrate the new 4.2 BufferedRenderer which makes front end filtering on 10,000 documents a reality

The code

The code is very simple and can be demonstrated easily by adding a ComboBox to the grid – in this example the combobox is appended to the grid and the values come from the state field in the grid store

new Ext.form.ComboBox({
        renderTo: 'gridHere',
        store: grid.getStore().collect('state')
    });

ext1

Filtering the grid reduces the data

ext2

and then running the add combobox again now creates one with a reduced set – NOT the uniquing of the values automatically

ext3

To see the returned value we just use firebug and execute the collect method – you can see that the values are returned as an array – this could then be parsed into any field.

ext4

Conclusion

Using the EXTJS grid can be very much akin to using a notes view – the trick is having the knowledge to know what is possible.

PS

The data is returned in the order that it is presented in the grid – if you want alphabetically then so an array.sort()

🙂

EXTJS in XPages #6 – remote sorting using the REST service

In this article I will demonstrate how use the remote sorting feature from within the EXTJS 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/xGridWithInfiniteScroller.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 we are able to utilize the infinite scroller to provide users a long list of documents to scroll through without taking up excessive memory at any one time. The local downside of this is that when you click on a column header to sort the column it only sorts the documents in view and not the whole list.

Using the sort property of the REST service we are able to perform this task simply and effectively.

The grid properties

Within the grid we have to signify that we are using remote sorting. We do this using the remoteSort property of the store:

remoteSort: true,

This tell the grid to use the remote sort capability when the user click on a column header.

The parameters sent to the REST service

When the user clicks on a column header a URL call is made to the REST service to reload the data. Two additional parameters are sent to the service through the Query_String “sort=” and “dir”

Parameters sent to the REST service

_dc 1363479015660
count 20
dir ASC
keys
limit 20
page 4
sort address
start 60

The sort parameter signifies teh field to be sorted and the dir (ASC or DESC) tells which direction

The View

Within the notes view, to make this capability possible, we must have the view column sortable in both directions.

ext1

This does have one potentially enormous downside……view index size. Magnified by the number of columns which all need to be sortable in both directions and especially if you have Readers and Authors fields in the documents. But I am sure you know about these risks and I will not bore you with them – just remember it is a consideration when using this capability.

The REST service

The REST service has two properties we will need: sortColumn and sortOrder. Looking closely at the sortOrder though we see that the REST service is expecting “ascending” or “descending” as its options. We are going to have to turn ASC and DESC into ascending and descending appropriately.

Within the REST service properties we can see the sortColumn and sortOrder options. Selecting to compute the sortOrder we add the following SSJS code to the property:

<xe:this.sortOrder>
<![CDATA[#{javascript:
	var direction = context.getUrlParameter('dir');
		switch(direction)
		{
		case 'ASC':
			return 'ascending'
			break;
		case 'DESC':
			return 'descending'
			break;
		default:
			return 'ascending'							}
		}]]>
</xe:this.sortOrder>

This code takes the incoming “dir” UrlParameter and returns the expected ascending or descending value based on the JavaScript switch/case statement.

In the sortColumn property we do a similar thing by taking the incoming UrlParameter, but in the case no changes are needed because the field name already matches the programmatic column name in the view

context.getUrlParameter('sort');

Conclusion
And that is it – we have seen in this article how we can use SSJS in a REST service property to change the incoming UrlPramter to return the value needed by the REST service and in doing that we have enabled remote sorting of view columns.

EXTJS in XPages #5 – Infinite scroller

In this article I will show how simple it is to turn your paging grid into an infinite scrolling one.

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/xGridWithInfiniteScroller.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 I show how to add a pager to your grid to prevent a significant number of documents from loading into your grid at once. This is more analogous to the view panel out of the box paging that we are familiar with in the XPages environment.

From a user interaction perspective paging is good and allows the user to sort through each 25 documents worth of data. But it fails miserably when you have to page more then 4 times to get more data. That task of having to page down through the data becomes very cumbersome very quickly.

Infinite scrolling

The concept of infinite scrolling was first made popular on websites like Facebook and Twitter. When the user gets to the bottom of the screen an icon appears indicating more data loading and then the user continues to scroll. This has now become the expectation of users trying to sift through a lot of data on the web.

Add in infinite scrolling to your grid

Adding infinite scrolling is as simple as 3 lines of additional code in your existing grid. This feature is only supported as of EXTJS 4.1 only.

1. We require the paging scroller library to be loaded
2. We add a line to the grid signifying that this is a buffered data set
3. We Did another parameter to allow a “scroll ahead” before the next data set is loaded.

Looking at the video below you can see that as the user starts to scroll the page seems to just go and go – and only when dragging the scroll bar down quickly do you even see a Loading Mask. Adding Firebug console you can see that the buffered pages are loaded into memory before the user gets to the point of the grid where they would need to be displayed.

Here is the code sections which have changed since the last article.


Ext.require([
       	'Ext.data.*',
       	'Ext.grid.*',
        'Ext.toolbar.Paging',
        'Ext.grid.PagingScroller'       //THIS LINE
]);

var store = Ext.create('Ext.data.Store', {
    // allow the grid to interact with the paging scroller by buffering
    pageSize: 25,
    buffered: true,               //THIS LINE
    leadingBufferZone: 25,        //THIS LINE
    autoLoad: true,
    autoDestroy: true,
    autoSync: true,
    model: 'Person',
    proxy: {
        type: 'rest',
            url: 'xRestService.xsp/byFirstName?count=25',
            reader: {
                type: 'json',
                root: 'items',
                totalProperty : '@toplevelentries'
            },
            writer: {
                type: 'json'
            }
        }
    });

Performance

This is a huge performance boost from loading all the documents into the grid at once. When using the grid purely as a reporting display model, you should consider using the infinite scrolling capabilities of the grid as a standard. There are some caveats though to be aware of; when it comes to interactivity with the grid data this can cause some challenges (see below)

This performance boost is most significant in IE and XPiNC where a few hundred documents can slow down the browser. With infinite scrolling you can have 10,000 documents in a view and scroll to any of them.

Better than Paging?

Absolutely:

  • A familiar interface for users experienced in using the notes client
  • A much faster way to find the user to find the page containing their document of reference.
  • Less bandwidth wasted guessing. Moving the scroll bar up and down to a visual guide is more accurate than guessing the number of the page where the document might be based on the sorting of the column in question.

What about filtering?

Infinite scrolling does have a couple of downsides if you want to use other parts of the grid functionality. Because you are only showing a few of the documents at a time – filtering the “loaded documents” is not going to give you anything and you have to go with remote filtering. Remote filtering is quite possible in the XPage environment but I am unable to share at this time as it was part of a client engagement where we implemented this.

What about sorting?

Also with sorting you can sort the local documents in the grid – but that doesn’t really help. You have to use remote sorting which can be done and we will look at in the next article.

Custom Control

Adding infinite scrolling to the custom control is easy because the only things which have changed are the 3 lines of Javascript. This has been updated in the demo database.

Conclusion

Paging is better than loading all documents into the grid at once. Infinite scrolling is better than Paging in most cases. Like everything in this series, there are pros and cons which have to be considered for each piece of functionality and it is important to understand them so that you can verbalize these considerations to your customer when architecting a solution.