CF8 Ajax Grid: Showing the TotalRecordCount

This morning, Paul Stewart submitted an interesting question to the CF-Talk List:

"Hi, I have been using the CFgrid tag in CF8 (format HTML, bound to a cfc), and i have got it do almost all that i need it do (see below). Except that i don't know how to get access to the TOTALROWCOUNT figure that i can see in the debugger returned from the CFC , and also be able to display that figure either on the bottom of the grid itself (using ext.toolbar???), or even above it on the page using another ajax method??.

I need to do this show the user amount of records returned from his/her search. i.e 'your search returned x number of records.' From reading Ray Camden's blog I know i have to use the underlying Ext library that the cfgrid tag is based on, and i have used his solution to format some columns in the grid (nice one Ray). But i am not really getting anywhere (hopelessly bamboozled) with the records returned figure...."

This is a great question! And so easy to implement using the underlying ExtJS 1.1.1 component architecture of the ColdFusion 8 Ajax controls. The grid component already knows what the beginning and ending rows are for the page, and the total number of rows, so it's just a matter of getting them to display on the PagingToolbar. Paul had included a little code in his post, the primary piece of which (as far as we're concerned) is his formatgrid() function:

formatgrid = function() {
mygrid = ColdFusion.Grid.getGridObject('ad');

cm = mygrid.getColumnModel();
cm.setRenderer(3,myf);
cm.setRenderer(5,myf);

mygrid.reconfigure(mygrid.getDataSource(),cm);
}

Basically he has most of the pieces that he needs already. He needs a reference to his data.Store object, which we'll add right after he gets his grid instance:

...
   mygrid = ColdFusion.Grid.getGridObject('ad');
   ds = mygrid.getDataSource();
   ...

Then, after his renderers, but before his reconfigure(), we'll get a reference to his grid's FooterPanel to redefine the PagingToolbar:

...
   cm.setRenderer(5,myf);
   // Get the Footer
   var gridFoot = mygrid.getView().getFooterPanel(true);
   // Create a new Paging Toolbar
   var paging = new Ext.PagingToolbar(gridFoot,ds,{
      pageSize:25, //number of records displayed in grid
      displayInfo:true,
      displayMsg:'Displaying records {0} - {1} of {2}',
      emptyMsg:"No records to display"
   });
   
   // reconfigure the grid
   mygrid.reconfigure(ds,cm);
   ...

Hey! Reload the page and what do you get? An adjusted toolbar, giving Paul exactly what he was looking for, the current record from - to and the total number of records returned. Though I've used this before, I wanted to note that this code is almost an exact copy of the example code included in the ExtJs 1.1.1 API documentation. There is a ton of untapped potential within the underlying Ext framework. With a little research you may be surprised what you can accomplish.

ExtJS 2.0 Grid Gotcha

I recently upgraded all of my former ExtJS code to utilize the updated 2.0 library. There are a lot of fantastic changes and additions here, but I did run into one or two snags. In a previous post I showed how to use Event Handlers and Item Renderers with the ColdFusion 8 Ajax implementation of the ExtJS 1.1.1 data grid. It works very similarly with a straight (non-CF) implementation, whereby I had defined an unbound column, in my ColumnModel, and applied a renderer to it so that an icon would appear with an onclick event attached to the cell click. It's a very straight forward process, that immediately broke with the 2.0 library. It took me a little bit of playing around, but I finally discovered what appears to be either a bug in the library, or a glitch in the documentation.

What made this column previously 'unbound' was the fact that no dataIndex was set to the column. It wasn't required. Acoording to the 2.0 documentation, the attribute is still supposed to be optional. However, when trying this with 2.0, I found that I would continually get an error. Only after duplicating my dataIndex (I applied my ID query column to the dataIndex, the same as my ID column), and then using the renderer to overwrite, did I finally get my working grid. It's a bit of a hack, but it did the job.

It's a small gotcha, but a gotcha none the less, so I wanted to let ya'll know. As I said before, there are some fantastic changes in the new 2.0 implementation, and already some nice plugins being developed by the community. Well worth taking a peek under the covers.

CF8 Grid Renderers and Events Source

I had several people ping me for some sample code for my CF8 Ajax Grid: Renderers and Events posting, on (what else) custom cell renderers and event listeners. I have just uploaded a zip of commented source code to that post. Just click the 'Download' link at the base of the post for all the source code goodness:)

My First ExtJS DataGrid Pt 7: Custom Cell Renderers

So, it's been awhile. No, I haven't forgotten you, I've just been busy with a lot of things. One of which has been implementing a new ExtJS DataGrid in a project I'm working on. Sure, there's a lot more going on, but that's becoming a nice front end piece. As previously promised, I want to look at a renderer.

[More]

My First ExtJS DataGrid Pt 6: The Grid

Alright, rolling right along. Last tutorial (see the bottom of this post for a complete listing) we covered the initial setup of our ColumnModel, which is telling our DataGrid what the basic layout of our grid columns will be and to which fields of our DataStore each column will be mapped to. Now it's time to actually instantiate our grid.

So, the first thing we have to do is create our Grid object and tell it which html element will be our grid within our page. Basically we'll tell the function the ID of the div element, what DataStore object to use, and which ColumnModel object to use.

// create the editor grid
var grid = new Ext.grid.Grid('topic-grid', {
ds: ds,
cm: cm
});

This is it in it's most basic form. We're going to stay away from any fancy stuff for now, and get to selection models and stuff in a later post. Let's add to it a little bit by stating that the grid may be resizable.

// make the grid resizable, do before render for better performance
var rz = new Ext.Resizable('topic-grid', {
   wrap:true,
   minHeight:100,
   pinned:true,
   handles: 's'
});
rz.on('resize', grid.autoSize, grid);

That will make the grid resizable, and should be declared prior to rendering the grid. Rendering the grid is our next step, and way simple.

// render it
grid.render();

Can't get much easier than that. Going back to the resizable for a second, don't ask, I don't know. Easiest one for me to figure on sight is the minHeight attribute, but I haven't reviewed the API enough to know what all is going on. If you figure it out before I do then leave a comment. Next we'll need to add the paging tool bar to the footer. We'll get the footer, then add the paging toolbar.

var gridFoot = grid.getView().getFooterPanel(true);

// add a paging toolbar to the grid's footer var paging = new Ext.PagingToolbar(gridFoot, ds, {
pageSize: 25,
displayInfo: true,
displayMsg: 'Displaying users {0} - {1} of {2}',
emptyMsg: "No users to display"
});

Notice the arguments of the PagingToolbar() function, the footer object, the DataStore object, and a JSON object with attributes of the pageSize (number of records), whether to display data set info, the message of the count, and a message to display should no records be returned.

The final step here is to load the DataStore. Once this is done you will have a complete, basic DataGrid for display.

// trigger the data store load
ds.load({params:{start: 0, limit: 25}});

Notice here the 'params'. These are name/value pairs that are passed, via post, whenever you request the next page of your data, with these values being your initial request (starting at row 0, returning 25 records). If you go back and look at your pagingService.cfm (included in the download) you'll see where these values are used.

So, that's the end of this post. You now have a basic DataGrid. In our next tutorial we'll start to style some things, and show you how to implement a custom 'renderer' for a specific column's data.

My First ExtJS DataGrid Pt 5: The ColumnModel

OK, we're winding down to the end of this tutorial, with only a few key components left. Today we cover the ColumnModel, which is how we manage the initial layout of our ExtJS DataGrid. We've already covered initial setup, our paging query, and defined out DataStore (see related entry links at the bottom of the post).

First things first, let's instantiate the ColumnModel

var cm = new Ext.grid.ColumnModel([{
   // cm is our ColumnModel object
}]);

Next we'll define the layout of the first column in our grid

var cm = new Ext.grid.ColumnModel([{
   id: 'fname',
   header: "First Name",
   dataIndex: 'vcFirstName',
   width: 120
}
}]);

Alright, pretty basic stuff here. We've placed an 'id' on this column. This allows you to later reference the column specifically for styling. We didn't really mark this one for a particular reason, we just did it to explain. Next we have the 'header', which is the text that appears in your column header at the top of your DataGrid. This is then followed by the 'dataIndex' to define the data column it is mapped to within your DataStore. Lastly we have the 'width' attribute, which speaks for itself.

There are several other possible attributes that are available to you here, most of which are fairly easy to grab from the ExtJS API. We'll cover a few more in our next tutorial, but for now we'll just complete the layout of the ColumnModel.

var cm = new Ext.grid.ColumnModel([{
   id: 'fname',
   header: "First Name",
   dataIndex: 'vcFirstName',
   width: 120
},{
   header: "Last Name",
   dataIndex: 'vcLastName',
   width: 120
   },{
   header: "Is Admin",
   dataIndex: 'bIsAdministrator',
   width: 40
   },{
   header: "Is Active",
   dataIndex: 'bIsActive',
   width: 40
   },{
   id: 'last',
   header: "Last Login",
   dataIndex: 'tsDateLastLogin',
   width: 150
}]);
// by default columns are sortable
cm.defaultSortable = true;

The order you work in will be reflected in your final initial layout. Each column definition is contained in curly braces, separated by commas. Each attribute is also comma delimited, with the attribute name being un-quoted, while their values are quoted if string values and not if numeric. Also notice the double quotes around the 'header' values, but the single quotes around the others. I don't know if this is intentional, and haven't really tested it, but this is the way it was in all of the example files so I thought it best to stick with the convention. The last thing we did here was set a directive on the ColumnModel to state the the columns will be sortable by default.

Alright, now you have defined your ColumnModel. A few steps left to go just yet, like the grid itself, custom renderers, styles, and other things to give it a little more cowbell. We'll begin wrapping those up in our next edition. For those coming to the Nashville CFUG Scorpio Tour presentation tomorrow night I hope you'll flag me down and say high.

My First ExtJS DataGrid Pt 4: The Data Store

So, up until now we have setup our support files and written our paging query service. Now it's time to begin tying our data to our DataGrid. The Ext library provides you many different ways of pulling in data into the components. We're going to create a data 'Store' using a combination of the HttpProxy (a utility for pulling data from within the same domain) and the XmlReader (for parsing our returned datasets).

A 'Store' is "a client side cache of Ext.data.Record objects which provide input data for widgets." Basically you create this representation of your server side data by defining where it is and what it looks like. We're using the HttpProxy, in this case, because our service script (pagingService.cfm) resides within the same domain as our calling page. And, since we set our service script to return an XML document, we need the XmlReader to 'map' the data that we need.

First we'll setup the basic block

var ds = new Ext.data.Store({

});

Add in the location of our service script

var ds = new Ext.data.Store({
   // load using HTTP proxy: new Ext.data.HttpProxy({url: 'http://cc.mytestserver.loc/jTesting/xmlSqlTest.cfm'}),

});

Then we set up our XML 'reader'

var ds = new Ext.data.Store({
   // load using HTTP proxy: new Ext.data.HttpProxy({url: 'http://cc.mytestserver.loc/jTesting/xmlSqlTest.cfm'}),

   // the return will be XML, so lets set up a reader reader: new Ext.data.XmlReader({
      // records will have an "T4" tag       record: 'T4',
      id: 'ID',
      totalRecords: "recCount"
   }, [
      // set up the fields mapping into the xml doc       'vcFirstName', 'vcLastName', 'bIsAdministrator','bIsActive','tsDateLastLogin'
   ]),

});

Ok, here is where I have to put on the breaks for a minute. You have to understand a little about what the reader requires here. It helps if you take a look at a return recordset from your service script. I suggest you call it in Firefox for a nice representation, but basically it looks something like this:

<userList>
   <T4>
      <recCount>5802</recCount>
      <ID>2350</ID>
      <vcFirstName>Robin</vcFirstName>
      <vcLastName>Williams</vcLastName>
      <bIsAdministrator>0</bIsAdministrator>
      <bIsActive>1</bIsActive>
      <tsDateLastLogin>2007-05-01T14:34:57</tsDateLastLogin>
   </T4>
   <T4>
      <recCount>5802</recCount>
      <ID>4027</ID>
      <vcFirstName>Howie</vcFirstName>
      <vcLastName>Mandel</vcLastName>
      <bIsAdministrator>0</bIsAdministrator>
      <bIsActive>1</bIsActive>
      <tsDateLastLogin>2007-04-29T16:29:33</tsDateLastLogin>
   </T4>
   ...
</userList>

You see, looking at the XML, that each record is denoted by the 'T4' node, which we have mapped in our reader to the 'record' attribute. You'll also note that the 'id' attribute was mapped to the 'ID' node in the XML document. This is a unique identifier within each record. We mapped 'totalRecords' to the 'recCount' node, as this is where we set up in our script to place the total record count, and then you see a basic comma delimited list of the nodes that will be included in our DataGrid.

It's important to note here that we have used a very basic XML return for our example here. You do have the power to map values from XML attributes and nested nodes, through the use of XPath syntax. You can even rename a 'field' when identifying a mapping. Look through the examples included in the ExtJS download to get a better idea of what you might be able to do.

OK, to finish our DataStore definition we're going to specify the ability to 'remotely' sort our data, and set up our default sort column and sort order.

var ds = new Ext.data.Store({
   // load using HTTP proxy: new Ext.data.HttpProxy({url: 'http://cc.mytestserver.loc/jTesting/xmlSqlTest.cfm'}),

   // the return will be XML, so lets set up a reader reader: new Ext.data.XmlReader({
      // records will have an "T4" tag       record: 'T4',
      id: 'ID',
      totalRecords: "recCount"
   }, [
      // set up the fields mapping into the xml doc       'vcFirstName', 'vcLastName', 'bIsAdministrator','bIsActive','tsDateLastLogin'
   ]),
   // turn on remote sorting    remoteSort: true
});
ds.setDefaultSort('vcLastName', 'desc');

And so begins our scripting for creating our DataGrid. The big "gotchas" that hit me along the way were the stupid things. Mis-identifying my 'record' mapping, or missing a trailing comma. Firebug and the JavaScript Console (Firefox) are your friends.

Next round we'll define our ColumnModel. This is how we'll define the order of initial column display, define column headings, and really button up the initial details before fine tuning our layout.

My First ExtJS DataGrid Pt 3: A Paging Query

OK, we're cooking with crisco now. You've probably taken a little time to look through the examples a little bit by now, and you've seen a little of what the paging grid looks like and can do, along with the many other examples. Our last tutorial covered setting things up, but before we dive into the JavaScript we'll need some data.

Now, the paging example that's included with the Ext download calls an external PHP page to retrieve the necessary JSON dataset. JSON is great, being small and lightweight, but I'm working with MS SQL at work, which can return XML data. Since the library has built in proxies for dealing with either, I change it up to take in the XML.

Each database has different ways of writing a 'paging' query. MySQL makes it really easy by providing multiple arguments for the LIMIT statement. MS SQL makes it a little harder. See, the trick is to only pull in the records, on each db call, that you actually need. Some people pull the entire recordset and then use a query-of-query to poll their required data, but if you're dealing with very large datasets then it makes more sense to only pull what your need when the time comes. I found a great article on MSDN (which I can no longer find) that gives a good suggestion on how to approach this, by using multiple sub select statements. But, the first thing we'll do is define some default parameters for those that will eventually be passed in on the AJAX calls.

<cfparam name="URL.start" default="0" />
<cfparam name="FORM.start" default="#URL.start#" />
<cfparam name="URL.limit" default="25" />
<cfparam name="FORM.limit" default="#URL.limit#" />
<cfparam name="URL.dir" default="DESC" />
<cfparam name="FORM.dir" default="#URL.dir#">
<cfparam name="URL.sort" default="vcLastName" />
<cfparam name="FORM.sort" default="#URL.sort#" />

First thing you probably noticed is that I have a FORM scoped variable that matches every URL scoped variable, defaulting the URL var first then defaulting the FORM var to the URL var's value. What this allows me to do is testing. I can call the page without any additional info and it will properly run, since I have defaulted all values, and I can tag on query string variables for initial output testing, or tap it directly from a form post. These variables are pretty basic: 'start' is the starting record row, 'limit' is the number of records to be returned, 'dir' is the sort order, and 'sort' is the column to sort on. After this we move to the query itself.

SELECT   (SELECT COUNT(ID) AS recCount FROM tblUsers) AS recCount,
         ID,
         vcFirstName,
         vcLastName,
         bIsAdministrator,
         bIsActive,
         tsDateLastLogin
   FROM ( SELECT TOP #FORM.limit# ID,
               vcFirstName,
               vcLastName,
               bIsAdministrator,
               bIsActive,
               tsDateLastLogin
         FROM (SELECT TOP #FORM.start + FORM.limit# ID,
                     vcFirstName,
                     vcLastName,
                     bIsAdministrator,
                     bIsActive,
                     tsDateLastLogin
             FROM (SELECT TOP #FORM.start + FORM.limit# ID,
                        vcFirstName,
                        vcLastName,
                        bIsAdministrator,
                        bIsActive,
                        tsDateLastLogin
                  FROM tblUsers AS T1
                  WHERE tsDateLastLogin IS NOT NULL
             ORDER BY #FORM.sort# ) AS T2
          WHERE tsDateLastLogin IS NOT NULL
               ORDER BY #FORM.sort# DESC ) AS T3
         WHERE tsDateLastLogin IS NOT NULL) AS T4
   WHERE tsDateLastLogin IS NOT NULL
   ORDER BY #FORM.sort# #FORM.dir#
   FOR      XML AUTO, ELEMENTS

Notice a few things here. I only call the columns that I need. The two inner most sub selects use the TOP functionality to retrieve the 'start' row number plus the 'limit', so if you 'limit' yourself to 25 records and you are now calling page 3 (which would start with row 50) then you would say in these statements 'retrieve the TOP 50+25 rows', with the first sub-select then only asking for the 'limit' of the TOP 25. This gives you the TOP 25 rows of 50+25. You also see that a COUNT was added to the first select. Although this number appears in each record as 'recCount', it also gives you the total number of records that could be returned, thereby giving us the ability to say 'these are rows 50 thru 75 out of 38,543 records.'

If you cfdump the query return you will see multiple query rows returned, but nothing like you might expect. We now have to convert the returned query into a properly formated XML string. For this I use a function that Andrew Powell showed us in a Spry presentation that he did for the Nashville ColdFusion User Group. This was something that one of his compadres at Universal Mind wrote, and that I've adjusted slightly here.

<cffunction name="sqlXMLtoCFXML" access="public" output="false" returntype="any">
   <cfargument name="doc" type="string" required="false" default="xml" />
   <cfargument name="qry" type="query" required="true" />
   <cfscript>
      var x = "";
      var y = "";
      var retXML = "";
      x = listFirst(ARGUMENTS.qry.columnList);
      for (y=1;y lte ARGUMENTS.qry.recordCount;y=y+1){
         retXML = retXML & ARGUMENTS.qry[x][y];
      }
      retXML = "<" & ARGUMENTS.doc & ">" & retXML & "</" & ARGUMENTS.doc & ">";
   </cfscript>
   <cfreturn retXML />
</cffunction>

Basically this will take your MS SQL query output and format it into a proper XML document, with the ability for you to also define the 'root' element (doc). I keep this function in a utility library so that I can call it at anytime. I then take the return of this and output it between some cfcontent tags with a type of 'text/xml' to get a dynamic xml doc to be consumed by these AJAX calls.

<cfcontent type="text/xml"><cfoutput>#sqlXMLtoCFXML(VARIABLES.qryReturned)#</cfoutput></cfcontent>

The Ext library makes the call to the pages via a form post, then inspects the XML return to map fields to their assigned grid columns.

But, that's another lesson. This wraps it up for today. Tune in next time (same Bat-time, same Bat-channel) for our next installment: Defining the DataStore.

P.S. Sample files will be added to this post sometime tomorrow.

The sample files are now included in the download area below. Let me know if you have any questions, comments, or war stories.

My First ExtJS DataGrid Pt 2: Setting Up

So, off we go. First things first, you'll need the JQuery and ExtJS libraries. I also found out (the hard way, since it's not in the install notes) that you'll need the Dimensions JQuery Plugin. The full ExtJS download contains the project core files, JS library 'adapter' files, all of the Ext components, examples, documentation, and a 'resource' directory of images and stylesheets to help you get started.

I start off by placing the necessary script tags in the header of my document. Order of placement is important.

<script type="text/javascript" src="js/jquery/jquery.js"></script>
<script type="text/javascript" src="js/jquery/plugins/dimensions.js"></script>
<script type="text/javascript" src="js/ext-1.0/adapter/jquery/ext-jquery-adapter.js"></script>
<script type="text/javascript" src="js/ext-1.0/ext-all.js"></script>
<script type="text/javascript" src="js/paging.js"></script>
<link rel="stylesheet" type="text/css" href="resources/css/ext-all.css" />

Now, for reference, you don't need the 'all' ext library for this to work, but I'm just doing personal testing right now and figured that it's easier than breaking things out at this point. The 'all' library is the complete component collection, core, and utilities within one script file. While nice to have it all, it is large, and you can use just the components you need. The 'Build your own Ext' section of the ExtJS site can show you all of the necessary dependencies to put together only what you need.

I'm going to copy the paging.js file out of the ExtJS example directory and place it in the root of my js directory. I'm going to adjust this existing file to create my first paging grid. I know that this works, so I might as well not re-write the wheel. You'll also noticed that I used the included stylesheet file from the resources directory. Now, with all of this in place, all I need is my container div that will hold my DataGride. In the body of my document I place the following container code:

<div id="topic-grid" style="border:1px solid #99bbe8;overflow: hidden; width: 665px; height: 300px;"></div>

And that's the end of the initial setup. In part 3 I'll cover creating a paging sql page that will only call the records needed for each 'view' in our paging grid, returning the records in an XML format to be consumed by our grid. Until then, take a good look at the 'examples' directory in your ExtJS download, as well as the API and Examples section of Learn area of the ExtJS site. Also, included in the download below you will find the complete document we created today.

My First ExtJS DataGrid Pt 1

I can write my own code. I can take a process, define a proper algorithm for addressing an issue, and tap it out. In fact I enjoy the art of writing code, it's kinda like working on puzzles day in and day out, and I like a good puzzle. On the other hand I think it's also smart not to re-invent the wheel. Many minds have dealt with the same problems that I encounter every day, and someone has probably found a solution by now. Why fight that?

[More]

BlogCFC v. 5.8.001 was created by Raymond Camden. Layout inspired by bluerobot.com., with some JQuery thrown in for fun.