Ask a Jedi: Removing Pagination from CFGRID

Nathan asks:

I am recently getting into CF8 and Ajax features and for doing so building a small application to add items to a cart. I have it setup currently that items are in a select box and a cfgrid shows some item information of the item selected. Since, at a time only one item can be selected, I want to remove the pagination that comes by default with cfgrid. I am able to do that if I get a result query and use the cfgrid with a query attribute, but if I go for a dynamic bind, the pagination controls are present.

This was an interesting problem. First, let's be sure we are clear on what Nathan is seeing. The sexy new CFGRID added in ColdFusion 8 allows you to either directly pass a query to the grid itself (with a query defined on the same page, or loaded via CFC, UDF, etc) or load it via Ajax. This is done with the bind attribute.

If you pass the query to the grid then the pagination controls are not displayed. For example:

Even if you set a pagesize attribute, it will be ignored and the entire query will be displayed in the grid. Now compare this a grid that loads it's data via the bind attribute:

So how do you get rid of the bar? I knew that ColdFusion provided a function, ColdFusion.Grid.getGridObject, that would give me direct access to the grid. I figured then it would be a simple matter of checking the Ext API docs to see if there was a method to hide the panel.

I dug... and I dug... but I was unable to find anything that made sense to me. Using Firebug and the console.dir command (think of it as a ColdFusion dump), I finally found this:

var myGrid = ColdFusion.Grid.getGridObject('entries');
myGrid.view.getFooterPanel().setVisible(false);
myGrid.view.refresh();

This seemed to work well. I then added an ajaxOnLoad() call to run the code on load. Here is the complete example:

<html>

<head>
<script>
function testit() {
var myGrid = ColdFusion.Grid.getGridObject('entries');
myGrid.view.getFooterPanel().setVisible(false);
myGrid.view.refresh();
}
</script>
</head>

<body>

<cfform name="test">
<cfgrid autowidth="true" name="entries" format="html" width="600" bind="url:getentries.cfm?page={cfgridpage}&pagesize={cfgridpagesize}&sort={cfgridsortcolumn}&dir={cfgridsortdirection}">
<cfgridcolumn name="id" display="false">
<cfgridcolumn name="body" display="false">

<cfgridcolumn name="title" header="Title">
<cfgridcolumn name="posted" header="Posted">
</cfgrid>
</cfform>
<cfset ajaxOnLoad("testit")>

</body>
</html>

I'm not sure this is the best way to do it (I've actually got an Ext book on the way to me for review purposes), but it seems to work ok. Another solution would have been to use the drop down to drive a bound cfdiv tag. That cfdiv then could have loaded both an inline query and cfgrid.

Comments

Nate's Gravatar Thanks for the post Ray. This is what I was looking for.

I am surprised that Adobe did not include some of these simpler options in their CF tags for use and that we have to dig into ExtJS to use them. There is an additional issue here that the grid look will change after the page has loaded (correct me here if I am wrong).

The advantage of using cfgrid with bind instead of using a cfdiv with an inline query and cfgrid is that cfdiv always shows the "loading graphic" whenever the select box is changed, while cfgrid with bind is much more dynamic. But the cfdiv solution sounds more pleasing to me right now than modifying the grid after it has loaded.
# Posted By Nate | 12/22/08 10:09 AM
Raymond Camden's Gravatar I think Adobe made some educated guesses as to what folks would _commonly_ do, and just added the JS API to cover the rest. :)
# Posted By Raymond Camden | 12/22/08 10:10 AM
Nate's Gravatar The "hacks" into the libraries are great for doing things like a custom renderer for the cfgrid, removing pagination (along with allowing us to remove row selection) to me seems like a basic function that should be a part of the cfgrid implementation.

On the topic of the ExtJS libraries, is the whole lib included by Adobe or are only certain aspects of it (like the grid libraries implemented)?
# Posted By Nate | 12/22/08 10:15 AM
Akira's Gravatar Nate, as far as I know everything that is in Ext 1.1 is included in the CFIDE folder.

Ray, since Ext has changed their licensing model and moved to 2, do you know if Adobe is going to be including Ext 2 in a future update?
# Posted By Akira | 12/22/08 11:44 AM
Raymond Camden's Gravatar @Akira: I'm not aware of any public statement. Personally, I think that - in GENERAL - you should use what is provided and documented only. Yes, the full Ext library may be there, but I'd stick to the official stuff. If you want to do more, then I'd grab my _own_ copy of Ext (it's what, 5k??) and use that version. Make sense?
# Posted By Raymond Camden | 12/22/08 12:52 PM
Akira's Gravatar Ray, makes sense but I don't like the GPL 3 that is on the new version of the Ext library. The old one isn't easy to find on their website.
# Posted By Akira | 12/22/08 1:03 PM
Raymond Camden's Gravatar Ah, well, I was speaking in general. Personally I'm not a big fan of Ext. It seems a bit hard to use. Of course, I said the same about jQuery, and now I'm getting more used to it.
# Posted By Raymond Camden | 12/22/08 1:05 PM
Anthony Patch's Gravatar How do you output the result set in getentries.cfm in order to populate the grid via the bind attribute? In otherwords, what is in the getentries.cfm file? Do you have to use queryConvertForGrid? If so how? Much appreciated,
Tony
# Posted By Anthony Patch | 1/15/09 5:03 AM
Raymond Camden's Gravatar Yep, queryConvertForGrid.
# Posted By Raymond Camden | 1/15/09 2:04 PM
Anthony Patch's Gravatar Thanks. After reading LiveDocs, I found that you have to use queryConvertForGrid with serializeJSON in order to output correctly. ex:
<cfoutput>#serializeJSON(QueryConvertForGrid(query, page, pagesize))#</cfoutput>

It works like a charm.
# Posted By Anthony Patch | 1/16/09 4:18 AM
Nando's Gravatar I stumbled upon another way to remove pagination, which is to create your own footer under the grid. If you do that, it replaces the pagination footer with yours.

Here is how I've implemented it, adding a button to add a record with only a large red "+" icon in the middle of the button, and a delete button with an icon and the words "Delete Selected".

function initGrid() {
      grid = ColdFusion.Grid.getGridObject("RecipeHerbGrid");
      var gridFoot = grid.getView().getFooterPanel(true);
      var bbar = new Ext.Toolbar(gridFoot);
      bbar.addButton({
      cls:"x-btn-text-icon",
      icon:"icons/add.png",
      handler:onAdd
      });
      bbar.addSeparator();
      bbar.addButton({
      text:"Delete Selected",
      cls:"x-btn-text-icon",
      icon:"icons/delete.png",
      handler:onDelete
      });
   }

Heck, while i'm posting this, I should also just go ahead and post the handler functions, in case it helps anyone. Took me a long time to pull all this together:

function onAdd(button,event){
      ColdFusion.Window.show('addRecipeHerbWindow');
      // console.log(button);
      // console.log(event);
   }
   
   function addRecipeHerb() {
      //send data to CFC to add RecipeHerb, the result will be handled by handleResult function above
      var f = document.frmRecipeHerb;
      dataproxy.addNewRecipeHerb (
         f.recipeVariationId.value,
         f.herbId.value,
         f.weight.value
      );
      ColdFusion.Window.hide('addRecipeHerbWindow');
      ColdFusion.Grid.refresh('RecipeHerbGrid', true);
   }
   
   function onDelete(){
   ColdFusion.Window.show('deleteRecipeHerbWin');
   }
   
   function deleteRecipeHerb(confirmMessage) {
   // if user choose to delete, then send the CFC call, otherwise just sit back!
   if (confirmMessage == 'yes')
   {
   var grid = ColdFusion.Grid.getGridObject("RecipeHerbGrid");
   var record = grid.getSelections();
   // remember, CF makes column names to all UPPERCASE, so dont forget to do that
   dataproxy.deleteRecipeHerb(record[0].data.RECIPEHERBID);
   }
   ColdFusion.Window.hide('deleteRecipeHerbWin');
   ColdFusion.Grid.refresh('RecipeHerbGrid', true);
   }
   
   function editRecipeHerb() {
      var grid = ColdFusion.Grid.getGridObject("RecipeHerbGrid");
      var record = grid.getSelections();
      dataproxy.editRecipeHerb (
         record[0].data.RECIPEHERBID,
         record[0].data.THEWEIGHT,
         record[0].data.PINYIN
      );
      ColdFusion.Grid.refresh('RecipeHerbGrid', true);
   }

It's all wired to the back end using the cfajaxproxy tag.
# Posted By Nando | 1/31/09 2:15 PM
Michael Sinaysky's Gravatar Raymond,

Can you publish code for the getentries.cfm?
# Posted By Michael Sinaysky | 6/26/09 8:03 PM
Raymond Camden's Gravatar This should be it. It uses a BlogCFC database.

<cfquery name="entries" datasource="blogdev">
select *
from tblblogentries
<cfif len(url.sort) and len(url.dir)>
order by #url.sort# #url.dir#
</cfif>
</cfquery>

<cfset data = queryConvertForGrid(entries, url.page, url.pagesize)>
<cfset encoded = serializeJSON(data)>
<cfcontent type="text/json" reset="true"><cfoutput>#encoded#</cfoutput>
# Posted By Raymond Camden | 6/26/09 9:48 PM
Michael Sinaysky's Gravatar Raymond,

I am receiving fallowing error:
window:global: 'e' is undefined (http://localhost/CFGrid_Test/test.cfm?cfdebug=true..., line 525)

This is a code for Test.cfm
<html>
<head>
      <script>
function testit()
      {
         var myGrid = ColdFusion.Grid.getGridObject('entries');
         myGrid.view.getFooterPanel().setVisible(false);
         myGrid.view.refresh();
}
</script>
</head>

<body>
<cfform name="test">
<cfgrid autowidth="true"
name="entries"
format="html"
width="600"
bind="url:getentries.cfm?page={cfgridpage}&pagesize={cfgridpagesize}&sort={cfgridsortcolumn}&dir={cfgridsortdirection}">
<cfgridcolumn name="id" header="ID">
<cfgridcolumn name="price" header="Price">
<cfgridcolumn name="product" header="Product">
</cfgrid>
</cfform>
<cfset ajaxOnLoad("testit")>
</body>
</html>

This is a code for getentries.cfm

<cfset data = queryNew("id,price,product")>
<cfloop from=1 to=10 index="x">
<cfset total = randRange(20,100) & "." & randRange(1,99)>
<cfset product = "Product #X#">
<cfset queryAddRow(data)>
<cfset querySetCell(data, "ID", x)>
<cfset querySetCell(data, "price", total+0, x)>
<cfset querySetCell(data, "product", product, x)>
</cfloop>

<cfset data = queryConvertForGrid(getArtists, url.page, url.pagesize)>
   <cfset encoded = serializeJSON(data)>
   <cfcontent type="text/json" reset="true"><cfoutput>#encoded#</cfoutput>
# Posted By Michael Sinaysky | 6/27/09 2:58 PM
Raymond Camden's Gravatar Not sure what to tell you. Do you still get an error if you comment out everything in testit?
# Posted By Raymond Camden | 6/27/09 3:00 PM
Michael Sinaysky's Gravatar Never mind I figure out what is going on.

Gecko (and similar) browsers pass a reference to an event object as the first parameter when calling a function assigned to an intrinsic event handler this way. IE doesn't, it uses a global 'event' variable.
Local variable "event" here masks IE's global event variable. As soon as I Modified function as fallowing

function testit()
{
var e = e || window.event;
var myGrid = ColdFusion.Grid.getGridObject('entries');
myGrid.view.getFooterPanel().setVisible(false);
myGrid.view.refresh();
}
everything starts working.

I do have another question s about CFGRID

How I can change background color of the one sell (let say cell in column 3) depending of cell value in column 2

Thanks in advance.
# Posted By Michael Sinaysky | 6/27/09 7:44 PM
Michael Sinaysky's Gravatar Raymond,

One more question. Is it possible to populate CFGRID through cfajaxproxy call?
# Posted By Michael Sinaysky | 6/28/09 1:07 PM
Raymond Camden's Gravatar Yea - you just have to ensure your JavaScript seeds the data right. I believe I did a blog entry on this before, but I can't remember the URL offhand.
# Posted By Raymond Camden | 6/28/09 9:49 PM
Michael Sinaysky's Gravatar Raymond,

If you be able to find blog URL I will be very much appreciated.

Did you sow my question about changing background color on the grid cell depending of value in another grid cell?
# Posted By Michael Sinaysky | 6/29/09 7:20 AM
Raymond Camden's Gravatar Not having any luck - may want to resort to Google.
# Posted By Raymond Camden | 6/30/09 9:42 PM