EXTJS in XPages #4 – Adding a Pager

In this article I will demonstrate how to add a pager control to your basic grid.

Demonstration

The EXTJS in XPages demonstration database can be found at http://demo.xomino.com/xomino/Extjs.nsf/xGridWithPager.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 from the previous articles we can make a basic EXTJ grid from a REST service. Loading a huge number of documents into an EXTJS grid does have its drawbacks though:

  • Poor user experience to have to search down through hundreds of records
  • Browser memory management issues.

Too many documents? *Warning*

Unlike traditional notes which allows for a personal cache of data for the user through the notes client, the web browser is not capable of doing this. Any browser (some worse than others) is going to run into problems when you load a significant amount of data on a page. In my experience the worst browser for this is the XPiNC browser (Firefox 3.5 code base), then IE then Firefox and Chrome.

As the developer you also have to consider:

  • load time
    • how long will it take to load hundres of documents from the REST service over your network
  • parsing time for the grid library
    • The EXTJS code has to take the JSON and create from it a gigantic table and add that to the DOM. This takes time and browser resources – too slow and the user gets impatient.

Recommendation

If you have more than 250 documents do not load them all into the grid at any one time. For all of the reasons above, starting with the user experience. While Notes Client or Excel may be able to allow a user to manage thousands of records, a web browser is not – find a better way.

Adding a Paging toolbar

There are only a couple of steps to adding a paging toolbar to the bottom of your grid.

First we need to add a property to the store – so that the grid knows how many documents are coming in

   var store = Ext.create('Ext.data.Store', {
        autoLoad: true,
        autoDestroy: true,
        autoSync: true,
        model: 'Person',
        proxy: {
            type: 'rest',
            url: 'xRestService.xsp/byFirstName?count=25',    // ?count=25 sets the number of docs the REST sends each time
            reader: {
                type: 'json',
                root: 'items',
                totalProperty : '@toplevelentries'     //This comes from the REST service
            },
            writer: {
                type: 'json'
            }
        }
    });

The REST service provides the @TopLevelEntries – this is the number of documents in the view


{
    "@timestamp":"2013-03-02T16:09:08Z",
    "@toplevelentries":1299,  // <----------
    "items":
    [
      {
          "@entryid":"1-66F3A5FE2B2A38CC85257B1B00254E6D",
          "@position":"1",
          "firstname":"Adam",
          "lastname":"Saenz",
          "address":"2519 Custer Street",
          "city":"ROCKWOOD (SOMERSET)",
          etc etc,

We then need to add the code to the EXT.require to include the Paging toolbar

Ext.require([
             	'Ext.data.*',
             	'Ext.grid.*',
                'Ext.toolbar.Paging',
         ]);

and finally add the toolbar to the grid itself by adding it as a property after the columns

        // paging bar on the bottom
        bbar: Ext.create('Ext.PagingToolbar', {
            store: store,
            displayInfo: true,
            displayMsg: 'Displaying topics {0} - {1} of {2}',
            emptyMsg: "No topics to display"
        })

And that’s it.

ext41

The 1 of 52 is calculated by the grid 1299 documents divided by 25 documents at a time = 52 pages

Custom Control update

We can add the pager to the XPages Custom Control in the same way as previously described in the last article – but in the case we need to add another property – how many documents per page instead of how many documents for the overall feed.

        proxy: {
		            type: 'rest',
		            url: '#{javascript:facesContext.getExternalContext().getRequest().getRequestURI()+"/ccREST?count="+compositeData.countNum}',
		            reader: {
		                type: 'json',
		                root: 'items',
		                totalProperty : '@toplevelentries'
		            },
<xe:restService pathInfo="ccREST" id="restServe1" >
	<xe:this.service>
		<xe:viewItemFileService  defaultColumns="true" viewName="${javascript:compositeData.theViewName}"></xe:viewItemFileService>
	</xe:this.service>
</xe:restService>

ext42

In this case you can see we are using the whole ByFirstName view and the generic Custom Control displayed all the columns successfully !

This Custom Control is available in the download database (ccEXTJSGridwithPager)

Advertisements

XPages SSJS: Beware – context.getURL() does not do what it says on the tin!

In working with the EXTJS grids I recently noticed an issue with the way I was getting the URL submitted to a REST service. The intention was to ready the query_string parameters created by the EXTJS grid filters, turn them into a notes understandable search string format and query the REST service. Seemed simple enough……

So to get the query_string initially I used context.getUrl() in the search property of the REST service. This returned the following when printed to the console (I have added carriage returns for clarity).

The important thing to look for is the filter[0][data][value]

print(context.getUrl().toString())

03/04/2013 12:36:20 PM HTTP JVM:
count=25&keys&_dc=1362420087840
&filter[0][field]=txtStatus
&filter[0][data][type]=list
&filter[0][data][value]=Sent
&page=1&start=0&limit=25&sort=GKO_DateLastMod&dir=DESC

The problem is that that was not the URL calling the REST service. The URL actually contains two parameters which are the same, but have different values.

…thedatabase.nsf/xRestService.xsp?count=25&keys=&_dc=1362420087840
&filter[0][field]=txtStatus
&filter[0][data][type]=list
&filter[0][data][value]=Draft
&filter[0][data][value]=Sent
&page=1&start=0&limit=25&sort=GKO_DateLastMod&dir=DESC

It would appear that the XSPContext object was somehow unique-ing the incoming parameters and losing the first one. Mild panic ensued and then I quickly realized there was another way to get the URL string using the facesContext…..(thanks to Julian Buss for the code)

var urlstring = facesContext.getExternalContext().getRequest().getQueryString()
var resultstring=java.net.URLDecoder.decode(urlstring,"UTF-8");
print(resultstring)

03/04/2013 12:36:20 PM HTTP JVM:
…thedatabase.nsf/xRestService.xsp?count=25&keys=&_dc=1362420087840
&filter[0][field]=txtStatus
&filter[0][data][type]=list
&filter[0][data][value]=Draft
&filter[0][data][value]=Sent
&page=1&start=0&limit=25&sort=GKO_DateLastMod&dir=DESC

This gave me the full string I was looking for which I was then able to conquer the world with (well OK the REST service anyway)

In Summary

using context.getUrl() on a URL with query_string that looks like this

&filter[0][data][type]=list&filter[0][data][value]=Draft&filter[0][data][value]=Sent

returned this which is wrong

&filter[0][data][type]=list&filter[0][data][value]=Sent

Don’t use: context.getUrl() to get the URL + Query_String
Use: facesContext.getExternalContext().getRequest().getQueryString()

2013 DCLUG reloaded (March 20th 11:30am)

I am happy to announce the first DCLUG event of 2013.

iconNoCommunityPhoto155

March 20th at the IBM offices on 14th St downtown DC.

11:30am – 1:30pm

IEG Briefing Center

600 14th Street, NW , Washington, DC

For more details please check out and sign up for the meeting at the “Meetup” page……

 

For more information please join the meetup group where the meetings will be announced

http://www.meetup.com/DC-Lotus-Professionals

and/or

We also have a community site on SocialBizUg.org which we will be updating with more information.

 

This will be the first of what we hope to be a full year’s of monthly meetings either at lunch or in the evenings in the DC area. The intention is to initially have short(ish) one person presentations (2hr meeting max) and we will see how that takes us through the year.

At this meeting I will be presenting my “jQuery: The world’s most popular JavaScript library comes to XPages” session which I gave at IBM Connect in January this year.

Please come and ask questions, share and broaden your knowledge/friends/peer group in the local area.

We have an list of local speakers looking to contribute to the group and also some well known IBM Domino community speakers from other parts of the USA interested in coming to speak and share their knowledge and experience with us.

This is going to be an exciting year and I encourage any/all of you in the DC area (and beyond) to come and join us.

EXTJS in XPages #3 – Creating a basic grid from a Custom Control

In this article I will demonstrate how to create an EXTJS grid representation of a view using a custom control and 3 custom properties. At the end of this article you will be able to drag and drop a custom control onto your XPage, give it the count, the viewName and the name of the placement element and a grid will be created for you without any further coding.

Introduction

In XPages we want to be able to genericize everything down to a re-usable custom control and in this example I will show you how to add a grid to your XPage doing just that. The following XPage source code is all that is needed to create this basic grid capability.

<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core" xmlns:xc="http://www.ibm.com/xsp/custom">

	<xc:ccBasicExtJSGrid countNum="10" putGridHere="gridHere" theViewName="ByName-First5Col" ></xc:ccBasicExtJSGrid>

	<div id="gridHere" class="extjsGrid"></div>

</xp:view>

Demonstration

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

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

The Custom Control

The Custom Control is created with 3 Properties which are populated when it is added to the XPage. You add it to your XPage by dragging and dropping and adding the 3 properties:

xt31

The View

The view ByName-First5Col is a simple view of 5 columns containing a single field in each column but as we will see this control will handle any number of columns.

ext32

The code

The end result is to create the JavaScript necessary to create a Basic grid in the same fashion as I did in the first article. But in this case we are not providing the column names or headers, we are programmatically getting them from the view itself.

The fields are created by going to the view and getting the columns and then of those, the item name (nvc.getItemName())

var fields=[
		#{javascript:
				var view:NotesView = database.getView(compositeData.theViewName)	//get the view
				var msg=[];
				for (var i=1; i<view.getColumnCount()+1; i++){
					var nvc:NotesViewColumn = view.getColumn(i);			//get each view column in turn
					msg.push('\''+nvc.getItemName()+'\'')				//get the column programmatic name
				}
				return msg.join(',')							//return the string of fields
		}
	]

The columns are created in the same maner but instead of getting just the column Item Name we get the column Title (nvc.getTitle() as well)

var columns=[
	#{javascript:
			var view:NotesView = database.getView(compositeData.theViewName)
			var msg=[];
			for (var i=1; i<view.getColumnCount()+1; i++){
				var nvc:NotesViewColumn = view.getColumn(i);
				//Create the JSON object containing the column programmatic name and the column title form the view
				msg.push('{header: \''+nvc.getTitle()+'\', sortable: true, dataIndex: \''+nvc.getItemName()+'\'}')
			}
			return msg.join(',')
		}
	]

I added the REST control to the Custom Control itself for simplicity and to make it more portable. This does have the restriction (at this time) of only having one grid on a page at a time. The pathInfo and id of a REST control cannot apparently be computed at build time so I have to keep them static. I am sure there is a way around it…in the mean time the viewName is passed in via compositeData as well as the count. As I think about it you could probably give control to the user using scoped variables and have them select the number of documents displayed…but that is for later.

	<xe:restService pathInfo="ccREST" id="restServe1" >
		<xe:this.service>
			<xe:viewJsonService defaultColumns="true" count="${javascript:compositeData.countNum}" viewName="${javascript:compositeData.theViewName}"></xe:viewJsonService>
		</xe:this.service>
	</xe:restService>

The viewName is also passed into the EXTJS code so that it knows where to look for the REST service on the page.

    var store = Ext.create('Ext.data.Store', {
        autoLoad: true,
        autoDestroy: true,
        autoSync: true,
        model: 'Person',
        proxy: {
            type: 'rest',
            url: '#{javascript:facesContext.getExternalContext().getRequest().getRequestURI()+"/ccREST"}',
            reader: {
                type: 'json',
                root: 'items'
            },
            writer: {
                type: 'json'
            }
        }
    });

The EXTJS code is then simplified because we are passing in the fields and columns values already computed above. Below is the code for the whole Custom Control.

<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core" xmlns:xe="http://www.ibm.com/xsp/coreex">
	<script type="text/javascript" src="ext-4.1.1/ext-all.js"></script>
	<link rel="stylesheet" type="text/css" href="ext-4.1.1/resources/css/ext-all.css" />

	<style>
		.icon-user { background-image: url(images/user.png) !important; }
		.icon-add { background-image: url(images/add.png) !important; }
		.icon-delete { background-image: url(images/delete.png) !important; }
	</style>

	<xp:scriptBlock id="scriptBlock1">
		<xp:this.value><![CDATA[

		var fields=[
			#{javascript:
					var view:NotesView = database.getView(compositeData.theViewName)
					var msg=[];
					for (var i=1; i<view.getColumnCount()+1; i++){
						var nvc:NotesViewColumn = view.getColumn(i);
						msg.push('\''+nvc.getItemName()+'\'')
					}
					return msg.join(',')
			}
		]

		columns=[
			#{javascript:
					var view:NotesView = database.getView(compositeData.theViewName)
					var msg=[];
					for (var i=1; i<view.getColumnCount()+1; i++){
						var nvc:NotesViewColumn = view.getColumn(i);
						msg.push('{header: \''+nvc.getTitle()+'\', sortable: true, dataIndex: \''+nvc.getItemName()+'\'}')
					}
					return msg.join(',')
				}
			]

		Ext.Loader.setConfig({
		  enabled : true,
		  disableCaching : false
		});

		Ext.require([
		             	'Ext.data.*',
		             	'Ext.grid.*',
		             	'Ext.ux.grid.FiltersFeature'
		         ]);

		Ext.define('Person', {
		    extend: 'Ext.data.Model',
		     fields: fields
		});

		Ext.onReady(function(){

		    var store = Ext.create('Ext.data.Store', {
		        autoLoad: true,
		        autoDestroy: true,
		        autoSync: true,
		        model: 'Person',
		        proxy: {
		            type: 'rest',
		            url: '#{javascript:facesContext.getExternalContext().getRequest().getRequestURI()+"/ccREST"}',
		            reader: {
		                type: 'json',
		                root: 'items'
		            },
		            writer: {
		                type: 'json'
		            }
		        }
		    });

		    var local = true;

		     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)

			};

		    var grid = Ext.create('Ext.grid.Panel', {
		        renderTo: 'gridHere',
		        frame: true,
		        features: [filters],
		        height: 400,
		        title: 'Users',
		        store: store,
		        iconCls: 'icon-user',
		        columns: columns
		    });
		});
			]]></xp:this.value>
	</xp:scriptBlock>

	<xe:restService pathInfo="ccREST" id="restServe1" >
		<xe:this.service>
			<xe:viewJsonService defaultColumns="true" count="${javascript:compositeData.countNum}" viewName="${javascript:compositeData.theViewName}"></xe:viewJsonService>
		</xe:this.service>
	</xe:restService>
</xp:view>

Results

As you can see below – a grid, same as we had before.

extjs33

But now we can add a new column to view and without changing the control itself the new column appears on the web.


ext34

ext35

And even complex columns with combined field values.

ext37 ext36

Conclusions

With this Custom control we are able to begin to create the ability to add these EXTJS grids to our XPages, very quickly and very effectively. As we go through the series I will update the custom control with the new capabilities as we look at new grids. Eventually we will have a plethora of controls which can be used to create powerful grids in next to no time at all.

In the mean time you can download the control from the sample database and play with it yourself. http://demo.xomino.com/xomino/Extjs.nsf

How to easily clear your windows printer queue

You know that moment when you have to desperately have to print something and there is a job in the printer queue which will not die without a restart of your computer and/or the printer and you curse and sear about how much you hate windows and printers??????

Copy the following into a text file and save as KillPrintQueue.cmd

net stop spooler
del %systemroot%\system32\spool\printers\*.shd
del %systemroot%\system32\spool\printers\*.spl
net start spooler

save it in your dropbox so you don’t lose it from machine to machine.

 

Run it

 

JOY TO THE WORLD !!!!

EXTJS in XPages #2 – Basic Grid capabilities

In this article I will demonstrate the basic out of the box functionality which comes with each grid and will focus on the capabilities of each column.

Introduction

In the previous article I demonstrated how to add a basic grid to your XPage using the EXTJS grid control and the ExtLib REST service. What we will look at in this article is the basic capabilities of the view columns.

Re-sizing columns

If you mouse over a column separator at the top you will see a familiar icon – clicking on the separator you can drag and drop the column increasing and decreasing the size

ext11 ext12 ext13

Re-ordering of columns

If you click on a column and “drag it” over to the left and right you can re-order them.

ext21

ext22

ext23

Hiding columns

If you mouse over the top right of a column you will see a drop down icon appear – click on it and you will be able to hide one of the columns. You can actually programmatically hide the columns when the grids open and have the users make them visible later if you want to.

ext25

ext26

Sorting Columns

If you click on the column header – or click on the small drop down and select to order the column you can do that

ext24

Filtering Columns

Well ok this is not a BASIC feature of the grid as it is added as another required library – but I figured I would show it cos it is COOL and should be a basic premise of the grids you create. Code will be provided in a later article but for now just know that it can be done locally (filtering the grid contents) and remotely (asking the server for a filtered results set) and it is in the basic demo site


ext6ext7ext8

Demonstration

Go look at the basic demo site and see for yourself.