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:
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.
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.
But now we can add a new column to view and without changing the control itself the new column appears on the web.
And even complex columns with combined field values.
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
I’m enjoying the series so far Marky. Looking forward to learn the best way to “open” a document using this technique.
Thanks Shean, I always appreciate feedback – glad you are enjoying it.
I am sticking to pure reporting for right now but it is certainly “do-able” in a number of different ways – we will get there 🙂
I would be very curious to know how you did it “Grid With Pager”.
Can you post an example?
The database that you shared is out of date.
thank you very much
Stefano – yeah the examples download database is in line with the blog series – the extra examples are stuff I have been working on for upcoming articles – will update the download database soon 🙂
thanks for reading 🙂
It appears the download link is broken.
Sorry I am running an update for tomorrow’s article. Please check again then.
Back up again thanks for the heads up
I played around with your examples. Here are my suggestions for optimization:
To use multiple custom controls of this grid I would extend the JS in the script block to dynamic variables for “fields” and “columns”, such as
var fields#{javascript:this.getParent().getId()}= …
so that the variable names are calculated with the id of the CC in the xpage.
Further I added a grouping feature to the grid:
‘Ext.grid.feature.Grouping’
…
(in the store optional a groupField: ‘state’ for example
…
in the grid: features: [filters, {ftype:’grouping’, groupHeaderTpl:'{name} ({children.length})’}]
Looking forward to see more 🙂
Thank you Oliver – nice suggestions!!
Yes Grouping – definitely going to get to that in probably article #9 or #10 with the new 4.2 BufferedRenderer infinite scroller which is just amazing!!
[…] EXTJS in XPages #3 – Creating a basic grid from a Custom Control […]