Custom grid renderers with CFGRID

I've been playing a bit with CFGRID lately (as have others) and I found that by taking a look at Ext's documentation, there are some very powerful features hiding within the CFGRID tag. ColdFusion provides a function, ColdFusion.Grid.getGridObject, that when used gives you a handler to the underling Ext Grid object. By looking at the documentation, you can see which methods are available with the grid. One in particular I thought was pretty neat was the ability to add custom column renderers. What is that?

Well imagine you have data being fed into the grid that you do not have control over. For example - perhaps you have price information that isn't formatted nicely. Turns out Ext has a set of built in formatters that you can apply to grid columns, one of them being a money formatter. What if you have some other logic? Maybe you want to flag a price that is lower than 10 dollars? Again, using the Ext API, you can write your own formatter just for this purpose.

I've only just begun to scratch the surface of Ext, but here is a quick and dirty example to give you an idea of what is possible.

First lets create a simple grid:

<cfset data = queryNew("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, "price", total+0, x)>
   <cfset querySetCell(data, "product", product, x)>
</cfloop>

<cfform name="test">
<cfgrid autowidth="true" name="data" format="html" query="data" width="600">
<cfgridcolumn name="price" header="Price">
<cfgridcolumn name="product" header="Product">
</cfgrid>
</cfform>

I'm using a hard coded query with products and prices. This is then fed directly into the grid. Note that I'm not using Ajax here.

Now let's look at how we can add a custom column renderer. The first thing we need to do is to set up the page to call a function when the grid is done loading. We do this with ajaxOnLoad:

<cfset ajaxOnLoad("testgrid")>

Because I'm not "properly" using Ajax on my page, I also added an import:

<cfajaximport/>

I've mentioned this hack before, and credit goes to Todd Sharp for discovering. Now let's look at the JavaScript:

myf = function(data,cellmd,record,row,col,store) {
   if(data == "Product 4") return "<b>" + data + "</b>";
   else return data;
}
testgrid = function() {
   mygrid = ColdFusion.Grid.getGridObject('data');
   cm = mygrid.getColumnModel();
   cm.setRenderer(0, Ext.util.Format.usMoney);
   cm.setRenderer(1,myf);
   mygrid.reconfigure(mygrid.getDataSource(),cm);
}

Skip the first function and focus in on testgrid. Testgrid is a horrible name but I was just playing around with the code so forgive me. I grab the grid using the ColdFusion.Grid.getGridObject API mentioned before. Everything after this is based on my reading of the Ext docs. I grab the columns using the getColumnModel function. I apply a renderer to columns 0 and 1 (you will never convince me that 0 based indexes make sense). The first renderer is a built one named usMoney. The second one is a custom function named "myf". usMoney will do what you imagine - format money. The second was hard coded to look for Product 4, and when found, bold it. I then use reconfigure to apply the column model back to my grid.

You can see an example of this here. Full source code is below.

<cfajaximport/>
<html>

<head>
<script>
      
myf = function(data,cellmd,record,row,col,store) {
   if(data == "Product 4") return "<b>" + data + "</b>";
   else return data;
}
testgrid = function() {
   mygrid = ColdFusion.Grid.getGridObject('data');
   cm = mygrid.getColumnModel();
   cm.setRenderer(0, Ext.util.Format.usMoney);
   cm.setRenderer(1,myf);
   mygrid.reconfigure(mygrid.getDataSource(),cm);
}
</script>
</head>

<body>

<cfset data = queryNew("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, "price", total+0, x)>
   <cfset querySetCell(data, "product", product, x)>
</cfloop>

<cfform name="test">
<cfgrid autowidth="true" name="data" format="html" query="data" width="600">
<cfgridcolumn name="price" header="Price">
<cfgridcolumn name="product" header="Product">
</cfgrid>
</cfform>

<cfset ajaxOnLoad("testgrid")>
</body>
</html>

Comments

Here's a direct links for the available renderers/formats if folks have trouble finding them in the docs...

http://extjs.com/deploy/ext-1.0.1/docs/output/Ext....
# Posted By todd sharp | 8/20/07 1:28 PM
Ray,

We have been working w/ the EXT package for about 5 months now and - in my very humble opinion - its the best UI package available. It is so well put together and so flexible that I can't imagine us ever using any other package. While the whole world seemed to be moving towards the heavier flex-based clients, we took a long hard look at the lighter JS ui packages, which, when combined w/ an AJAX package, seemed to give us almost the same functionality as Flex w/out the heavy load times.

The grid component in Flex is only one of the outstanding widgets, though that one is a superstar in its own right (the fact that it has the paging capabilities, the fact that it integrates so seamlessly w/ its dataset....I get all giddy just thinking about it :>), but the tabs, the dialog/windowing, the drag/drop capabilities, the tree.... all fairly lightweight, and all beautiful as hell.

Again, we've been using it fairly heavily for the last 5 or six months. If your not overly experienced w/ JS, the learning curve can be steep (a price I had to pay my own self), but it is WELL worth the time required to bring yourself up to speed. And if you need any help, I can testify from personal experience that the forums on the site are highly active and extremely helpful/useful.

I don't mean to sound like a PR agent for Jack Slocum, but we looked for quite a while for a package that would allow us to build an attractive, interactive UI (like Flex) but without a heavy load time. Ext was *EXACTLY* what we were looking for.
# Posted By Neil | 8/20/07 1:41 PM
Neil - I don't mind you PRing it at all. I think Ext seems _very_ slick from the _limited_ amount of stuff I've seen. I've read parts of the docs before - and frankly, I find it VERY difficult to grasp. Not impossible - just hard as heck. I think Spry has made me lazy. ;) I wish it were easy to use. My plan was to work on an AIR demo (I have one in mind in particular) and use Ext for the layout. It's one more thing I have "in mind" for my free time. ;)
# Posted By Raymond Camden | 8/20/07 1:44 PM
Ray,

Believe me, I understand EXACTLY what you mean when you say its confusing - I had the hardest damn time understanding what was going on (and I *thought* I was fairly good w/ JS; I was wrong). But I cannot stress enough how worth it the investment is. If you have anything else on your list of stuff to take a HARD look at, bump this package to the top. Yes there are a lot of methods and properties and whatnot. Yes the documentation is............ummmmm....incomplete in places :) but the code itself is EXTREMELY well documented and fairly easy to understand, so if you have any questions just roll up your sleeves and check it out.

Also, like I said, the forums are very, very active, and while there seems to be this one guy named animal up there that doesn't like people who don't have his experience level and understanding of how the package works, other than him I can personally attest that most of the time people bend over backwards to see that you get pointed in the right direction. And obviously, after all the polite little shoves in the right direction YOU have given ME, if you ever needed a hand, let me know, and I will do what I can to get you on the right track :)
# Posted By Neil | 8/20/07 1:52 PM
never really used gfgrid before and today when I was playing with this I started dropping a cfgrid into a cfwindow and kept getting an error:

"http: Error processing JavaScript in markup for element testgrid_body: "

I tried cfajaximport for cfform and cfwindow etc but still get it. Am I missing something dumb here from my lack of gfgrid experience?
# Posted By DK | 8/20/07 2:34 PM
besides my inability to use c over g =\
# Posted By DK | 8/20/07 2:34 PM
DK: Share your code, will help you get it figured out. Sounds like something simple.
# Posted By todd sharp | 8/20/07 3:02 PM
If you copy the code above and just dump it into a cfwindow from a calling page using the .create and change the format to flash from html you will see what I mean.
# Posted By DK | 8/20/07 3:09 PM
I think - and do NOT quote me on this - but I think I've seen issues with cfwindow and pointing to stuff that has Flash. If I remember right the issue is two fold:

Stuff inside cfwindow that uses JS should use foo = function format, not function foo().

CF's Flash stuff uses function foo() format for it's JS.
# Posted By Raymond Camden | 8/20/07 3:13 PM
Theres also a decent tutorial from 2005 on the older version of cfgrid on doing something similar using the labelfunction to combine or reformat labeling.

http://www.asfusion.com/blog/entry/using-labelfunc...

I'm curious though, and I know its getting a bit specific, but if its something like formatting for price wouldn't you rather just do it in your query to begin with and save yourself some code?
# Posted By DK | 8/20/07 3:50 PM
DK, I normally would do it server side too. I mentioned that above. However you may not have control over the data. Also - somethings don't make sense server side. Imagine a case where I want to highlight low prices. Sure I could do that server side, but it's really a client side concern.
# Posted By Raymond Camden | 8/20/07 3:54 PM
DK: Here's a way to get the Flash Form to load in a cfwindow. Note that the renderer code that Ray has above will not work, but if you were curious as to why the form won't load this should explain it.

http://cfsilence.com/blog/client/index.cfm/2007/8/...
# Posted By todd sharp | 8/20/07 4:07 PM
Ray,

Thanks for posting this. I have been playing around with getting a custom grid renderer using the ext object for a while but then got side-tracked.

When I asked adobe about it the official word is that the cfgrid didnt support custom cell renderers.
# Posted By Gary Gilbert | 8/20/07 4:10 PM
And I think it is still true - I wouldn't call what I did official. Well... wait. I'm not sure I'd call it dangerous either.

This Ajax stuff kinda opens the door a bit on what is supported/not supported.
# Posted By Raymond Camden | 8/20/07 4:12 PM
This is great, I would require to display the money in other languages as well, by modifying the .js file from extjs will Coldfusion 8 use that modified version when setting the cell renderer?
# Posted By Raul Riera | 8/20/07 7:28 PM
You would not want to modify the core file. You would just write your own function, as I did in the example above. Notice I use both a built in function and my own function.
# Posted By Raymond Camden | 8/20/07 7:51 PM
This 'simple' solution to the cell renderer problem opens the door on a whole lot of things we can do with the cells.

Your previous example where you built the html in the record set isn't needed anymore you can simply return the image path and build the img tag on the client side in the cell renderer.
# Posted By Gary Gilbert | 8/21/07 2:31 AM
These new Ext UI tags are very high on the "cool" factor with their functionality and ease of use, but I don't find them at all practical.

500KB in JS is a lot of page weight for simple datasets, and these controls don't downgrade gracefully. In real world implementations you'd potentially have thousands of rows of data and client side sorting will lose any usefulness.

Column reordering resizing are just candy, and not worth their weight.

If I were to invest the time in making these actually useful I'd be better off investing in building a Flex app. There is a tipping point in there if your app really needs this type of GUI.

Just my .02... I just wish Adobe would have spent the time enhancing the CFS (ColdFusion scripting) language instead of implementing a lot of unusable enhancements. *The performance increase in CF8 is awesome though*
# Posted By Brett | 8/22/07 6:35 AM
Actually, thousands of rows are a perfect example of where Ajax shines. You can let a user browser thousands of rows w/o having to reload the entire page each time. CF makes it easy to bind a grid to a 'page' of data. This to me is a -very- usable enhancement. The weight of some of these UI components are high - I will admit that - but that doesn't make them unusable. It all depends on your audience.
# Posted By Raymond Camden | 8/22/07 7:33 AM
These are really cool. Has anyone had any luck using Ext's Drag and Drop features for the Grid through ColdFusion.Grid.getGridObject?
# Posted By Scott | 8/23/07 12:09 AM
Scott, I played with that a long while back. I could enable the drag, but couldn't get a drop target to accept it. Maybe I'll play with it some more to see what we can come up with.
# Posted By todd sharp | 8/23/07 9:26 AM
Hello Todd,
Thanks for the reply.
That is pretty much where I'm at now, Drag but no Drop :( .
But I'm getting really close, I've found that I can call any of Ext's functions since cfAjaxImport brings in all of Ext's libraries.

I will get this working, and I'll post some code for others looking once I do :).

- Kind Regards,
Scott
# Posted By Scott Reynolds | 8/24/07 7:39 AM
A BIG HUG from me
# Posted By Roben Rajan | 10/10/07 1:17 AM
I have added the render function successfully using a class on a span. But I still cant get rid of the underline that is put on the text if href is used.

I have two styling questions for an html cfgrid:
How do I get rid of the underline, and how to I stop the first line from being selected.

I have made a style sheet that loads after the grid and changes the default styles that the grid uses, but I cant seem to control those two visible elements.

- Thanks for you help

- Tony
# Posted By Tony | 10/10/07 8:00 PM
This is exactly what I need to format a money column. I have an html cfgrid that uses the "bind" and "onchange" ajax functionality to call cfc's. I copied your example and it works fine as stand alone so I know I have all the proper plumbing in place.

I don't want to apply the cell rendering on form load but rather on the cfgrid "bind" and "onchange" events. I'm still coming up to speed on javascript. I'm not sure where to call the script from?

I also under stand that I need to change the gridname from 'data' to my 'WebChecks' and that i should be referencing column '12' and not 0 for the usmoney format.

my grid:

      <cfgrid name="WebChecks"
       bind="cfc:webChecksFunctions.getAllWebChecks({cfgridpage},{cfgridpagesize},{cfgridsortcolumn},{cfgridsortdirection},{clientid@change},{ckacctid@change},{afterDate@change})"
         bindonload="no"
         colheaderalign="center"
         colheaderbold="yes"
         colheaders="yes"
         delete="yes"
         deletebutton="Delete Row"
         format="html"
         gridlines="yes"
         onchange="cfc:webChecksFunctions.updWebCheck({cfgridaction},{cfgridrow},{cfgridchanged})"
         pagesize="6"
         rowheaders="yes"
         selectmode="edit"
         sort="yes">
         <cfgridcolumn name="id" header="id" width="80" display="no">
         <cfgridcolumn name="clientid" header="ClientID" select="no" width="80" display="no">
         <cfgridcolumn name="clientnumber" header="Company" select="no" width="80" display="no">
         <cfgridcolumn name="payeetype" header="payeetype" select="no" width="100" display="no">
         <cfgridcolumn name="ckacctid" header="CkAcctID" select="no" width="80" display="no">
         <cfgridcolumn name="ckdate" header="Date" select="yes" width="60" display="yes">
         <cfgridcolumn name="cktype" header="d/c" select="yes" width="30" display="yes">
         <cfgridcolumn name="cknumber" header="Nbr" select="yes" width="50" display="yes">
         <cfgridcolumn name="payeeid" header="payeeid" select="no" width="80" display="no">
         <cfgridcolumn name="payeename" header="Payee/Source" select="yes" width="120" display="yes">
         <cfgridcolumn name="payeeaddress" header="Address" select="yes" width="60" display="no">
         <cfgridcolumn name="ckmemo" header="Purpose/Store" select="yes" width="200" display="yes">
         <cfgridcolumn name="ckamount" header="Amount" select="yes" width="60" display="yes">
         <cfgridcolumn name="ckprinted" header="printed?" select="no" width="30" display="no">
         <cfgridcolumn name="postedflag" header="posted?" select="no" width="30" display="no">
         <cfgridcolumn name="tenninetynine" header="1099?" select="yes" width="55" display="yes">
      </cfgrid>


My CFC:

<cfcomponent>

   <!--- getAllWebChecks --->
   <cffunction name="getAllWebChecks" access="remote" output="no">
      <cfargument name="page">
      <cfargument name="pagesize">
      <cfargument name="gridsortcolumn">
      <cfargument name="gridsortdirection">
      <cfargument name="clientid">
      <cfargument name="ckacctid">
      <cfargument name="afterDate">
      <cfif (gridsortcolumn eq "")>
       <cfset gridsortcolumn = "cknumber">
      </cfif>
      <cfif (gridsortdirection neq "ASC")>
       <cfset gridsortdirection = "DESC">
      </cfif>
      <cfset sqlline = "select id, clientid, clientnumber, payeetype ckacctid,cknumber,CONVERT(CHAR(8),CKDATE,10)AS ckdate, ckamount, ckamount, cktype, payeeid, payeename, payeeaddress, ckmemo,ckprinted, postedflag, tenninetynine from webchecks where clientid = '#clientid#' and ckacctid = '#ckacctid#' and ckdate >= '#afterdate#' order by #gridsortcolumn# #gridsortdirection#">   
   <cfquery name="webChecks" datasource="#application.dsgl#" username="#application.user#" password="#application.pswd#">#PreserveSingleQuotes(sqlline)#</cfquery>
      <cfreturn QueryConvertForGrid(webChecks,page,pagesize)>
   </cffunction>
   
   <!--- updWebCheck --->
   <cffunction name="updWebCheck" access="remote" output="no">
      <cfargument name="gridaction">
      <cfargument name="gridrow">
      <cfargument name="gridchanged">
      <cfif isStruct(gridrow) and isStruct(gridchanged)>
         <cfif gridaction eq "U">
            <cfset colname=structkeylist(gridchanged)>
            <cfset value=structfind(gridchanged,#colname#)>
            <cfset sqlline = "update WebChecks set #colname# = '#value#' where id = '#gridrow.id#'">
            <cfquery name="updWebCheck" datasource="#application.dsgl#" username="#application.user#" password="#application.pswd#">#PreserveSingleQuotes(sqlline)#</cfquery>
         <cfelse>
            <cfset sqlline = "delete from WebChecks where id = '#gridrow.id#'">
            <cfquery name="delWebCheck" datasource="#application.dsgl#" username="#application.user#" password="#application.pswd#">#PreserveSingleQuotes(sqlline)#</cfquery>
         </cfif>
      </cfif>
   </cffunction>
</component>

thanks for looking.

Craig
# Posted By Craig | 10/20/07 1:29 PM
How can apply the same on row level?

i.e. change the color of the row, if a flag in the query is 1
# Posted By Asim M. | 10/22/07 11:27 AM
# Posted By todd sharp | 10/22/07 11:56 AM
Thx todd,
but this is also related to column. I am try to find something based on the row.

For example, if I had a list of tasks, I'd like to change the background color of the row if the task is urgent... Urgent is a field in the query object returned by CFC

in my case, the urgent column is not displaying the the grid. so changing the color in the column will not have any effect.
<cfgridcolumn name="urgent" display="false">
# Posted By Asim M. | 10/22/07 12:27 PM
I am also looking for something to change the row color based on a value from the query. Any luck?
# Posted By Connor M | 10/24/07 4:35 PM
I expend a lot of time to figure that out, but because of less documentation on cf side, I was unable to accomplish, so finally I decided to go with the column color change for now.

HTH
# Posted By Asim M | 10/24/07 4:52 PM
Okay, so I can get this code to work with "usMoney". But, I have a column that contains a smalldatetime coming from the database, so it look like this:

2002-03-01 00:00:00.0


And I need it to look like this:

3/1/2002

Without modifying the query.

According to the Ext docs, I should be able to change your example code to this:

cm.setRenderer(1, Ext.util.Format.date);

but when I do so, it does not render the column at ALL, and I get this error (twice) in the Javascript console:

Error: v.dateFormat is not a function
Source File: http://rwstage.advantex-internal.net/CFIDE/scripts...
Line: 15


Finally, it would seem that the column numbers ARE in fact a 1-based index, because in my experiment, my date column was the second column, and I had to use *2* (not 1) in order to get your example code to work in that situation.

Any advice?
# Posted By Marc Funaro | 11/9/07 8:16 AM
@Marc

I figured out how to get a date renderer to work. I posted it here:

http://www.coldfusionguy.com/ColdFusion/blog/index...
# Posted By Scott Bennett | 11/27/07 3:37 PM
if I cut and paste your code directly into a new page and run it on my server the render has no effect. is there some mapping or path problem?
# Posted By Michael White | 1/4/08 9:25 AM
if I use cfdebug in the url, it says global: testgrid is not defined. I think the javascript function is unavailable for some reason. in the past I fixed this by putting the javascript inside the cfform tag, not this time.
# Posted By Michael White | 1/4/08 9:34 AM
I think I figured it out. onRequestStart in my application.cfc loads a navigation header that has html head and body tags. If I get rid of that it seems to work
# Posted By Michael White | 1/4/08 9:50 AM
I'm still trying to get up to speed, but to format money values how would you have it include commas (i.e. $5,250.00). In my implementation I'm not getting commas.

Thanks,
# Posted By Brian | 2/6/08 9:48 AM