One of the coolest things about the ColdFusion 8 implementation of the CFGrid tag is that you can do a lot of customization, if you know your way around the Ext objects. I have found several blog entries about using custom renderers with the CFGrid tag. However, could not find a working example of one for date fields, so I decided to build one.
This example uses the coupons table in the cfbookclub database that comes with ColdFusion. There are two files in this example, coupons.cfc contains the functions needed for the editable CFGrid, and CouponForm.cfm contains the CFGrid Code and the JavaScript needed to do the date rendering. You will notice I had to specifically import the /CFIDE/scripts/ajax/ext/package/date.js file because it is not automatically imported with the cfgrid functionality. If you don't include that script tag you will get the JavaScript error "v.dateFormat is not a function".
CouponForm.cfm
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>Custom Date Renderer</title>
<!--- import the Ext date package --->
<script src="/CFIDE/scripts/ajax/ext/package/date.js" type="text/javascript"></script>
<!--- create javascript function for rendering dates --->
<script language="JavaScript" type="text/javascript">
setDateRenderer = function(){
mygrid = ColdFusion.Grid.getGridObject('CouponsGrid');
cm = mygrid.getColumnModel();
cm.setRenderer(3, Ext.util.Format.dateRenderer('m/d/Y'));
mygrid.reconfigure(mygrid.getDataSource(),cm);
}
</script>
</head>
<body>
<!--- Set up the Grid --->
<cfform id="CouponForm" name="CouponForm">
<cfgrid name="CouponsGrid"
format="html"
pagesize="10"
striperows="yes"
selectmode="edit"
bind="cfc:coupons.getCoupons({cfgridpage},{cfgridpagesize},{cfgridsortcolumn},{cfgridsortdirection})"
onchange="cfc:coupons.editCoupon({cfgridaction},{cfgridrow},{cfgridchanged})">
<cfgridcolumn name="Couponid" display="false" />
<cfgridcolumn name="SPONSORID" header="Sponsor" width="100"/>
<cfgridcolumn name="COUPON" header="Coupon" width="100"/>
<cfgridcolumn name="EXPIRATIONDATE" header="Exp Date" width="200"/>
</cfgrid>
</cfform>
<!--- use AjaxOnLoad to set the date renderer --->
<cfset ajaxOnLoad("setDateRenderer")>
</body>
</html>
</html>
coupons.cfc
<cfcomponent output="false">
<cfset THIS.dsn="cfbookclub">
<!--- Get art by media type --->
<cffunction name="getCoupons" access="remote" returnType="struct">
<cfargument name="page" type="numeric" required="yes">
<cfargument name="pageSize" type="numeric" required="yes">
<cfargument name="gridsortcolumn" type="string" required="no" default="">
<cfargument name="gridsortdir" type="string" required="no" default="">
<!--- Local variables --->
<cfset var data="">
<!--- Get data --->
<cfquery name="data" datasource="#THIS.dsn#">
SELECT COUPONID, SPONSORID, COUPON, EXPIRATIONDATE
FROM COUPONS
</cfquery>
<cfset qryFinalQuery = QueryNew("COUPONID, SPONSORID, COUPON, EXPIRATIONDATE","varchar,varchar,varchar,varchar") />
<cfloop query="data">
<cfset QueryAddRow(qryFinalQuery) />
<cfset QuerySetCell(qryFinalQuery, "COUPONID", "#data.COUPONID#") />
<cfset QuerySetCell(qryFinalQuery, "SPONSORID", "#data.SPONSORID#") />
<cfset QuerySetCell(qryFinalQuery, "COUPON", "#data.COUPON#") />
<cfset QuerySetCell(qryFinalQuery, "EXPIRATIONDATE", "#DateFormat(data.EXPIRATIONDATE, 'mm/dd/yyyy')#") />
</cfloop>
<!--- And return it as a grid structure --->
<cfreturn QueryConvertForGrid(qryFinalQuery,
ARGUMENTS.page,
ARGUMENTS.pageSize)>
</cffunction>
<!--- Edit an coupon --->
<cffunction name="editCoupon" access="remote">
<cfargument name="gridaction" type="string" required="yes">
<cfargument name="gridrow" type="struct" required="yes">
<cfargument name="gridchanged" type="struct" required="yes">
<!--- Local variables --->
<cfset var colname="">
<cfset var value="">
<!--- Process gridaction --->
<cfswitch expression="#ARGUMENTS.gridaction#">
<!--- Process updates --->
<cfcase value="U">
<!--- Get column name and value --->
<cfset colname=StructKeyList(ARGUMENTS.gridchanged)>
<cfset value=ARGUMENTS.gridchanged[colname]>
<!--- Perform actual update --->
<cfquery datasource="#THIS.dsn#">
UPDATE COUPONS
SET <cfswitch expression="#colname#">
<cfcase value="EXPIRATIONDATE">#colname# = #createodbcdatetime(value)#</cfcase>
<cfdefaultcase>#colname# = '#value#'</cfdefaultcase>
</cfswitch>
WHERE COUPONID = #ARGUMENTS.gridrow.COUPONID#
</cfquery>
</cfcase>
<!--- Process deletes --->
<!--- <cfcase value="D">
<!--- Perform actual delete --->
<cfquery datasource="#THIS.dsn#">
DELETE FROM COUPONS
WHERE contactid = #ARGUMENTS.gridrow.COUPONID#
</cfquery>
</cfcase> --->
</cfswitch>
</cffunction>
</cfcomponent>
Click Here to See a Working Example
You will also notice in the getCoupons function in coupons.cfc, that there is a bit of a work around going on there. The issue is that the QueryConvertForGrid function does not mesh well with date fields, it automatically puts them into an uncommon date format that most people wouldn't want to use. I found a post on Gary Gilbert's blog that shows how to resolve this problem by rebuilding the query and forcing the date field to be a varchar field with the formatting you want.
http://extjs.com/deploy/ext/docs/output/Ext.util.F...
Indeed, I tried that first but I couldn't figure out how to get it to work. I had my setDateRenderer function set up like:
setDateRenderer = function(){
mygrid = ColdFusion.Grid.getGridObject('CouponsGrid');
cm = mygrid.getColumnModel();
cm.setRenderer(3, Ext.util.Format.dateRenderer('m/d/Y'));
mygrid.reconfigure(mygrid.getDataSource(),cm);
}
But, I kept getting the error "v.dateFormat is not a function".
That's when I decided to build my own function for formatting the date. At first I built it using functions in another dateFormat.js file I have used in the past, but then I realized that the CFIDE folder probably had the EXT date.js file somewhere, so I found it and used that instead.
After your comment I looked at it further, and It turns out that all I really needed to do was add that script tag importing the date.js file, that is why I was geting the "dateFormat is not a function" error.
<script language="JavaScript" type="text/javascript">
formatDates = function(data,cellmd,record,row,col,store){
if(!data){
return "";
}
if(!(data instanceof Date)){
data = new Date(Date.parse(data));
}
return data.dateFormat("m/d/Y");
}
setDateRenderer = function(){
mygrid = ColdFusion.Grid.getGridObject('CouponsGrid');
cm = mygrid.getColumnModel();
cm.setRenderer(3, formatDates);
mygrid.reconfigure(mygrid.getDataSource(),cm);
}
</script>
<cfajaximport tags="cfinput-datefield">
to import the date.js file instead of:
<script src="/CFIDE/scripts/ajax/ext/package/date.js" type="text/javascript"></script>
but it works either way.
One thing I noticed using an editable grid is that the renderer doesn't seem to affect the format once I click on a cell in the grid to edit it - so that when editing it shows a data as July, 10 2007 00:00:00 but when not editing it shows it as 2007-07-10. Any idea how to change the format for that as well so that I can send the data back as it is shown when not editing? Thanks.
If you read the last paragraph of myblog entry above you will see that I touched on the subject you are referring to and I referenced that same entry from Gary Gilbert's blog.
<cfset QuerySetCell(qryFinalQuery, "EXPIRATIONDATE", "#DateFormat(data.EXPIRATIONDATE, 'mm/dd/yyyy')#") />
would be like:
<cfset QuerySetCell(qryFinalQuery, "EXPIRATIONDATE", "#DateFormat(data.EXPIRATIONDATE,'mm-dd-yyyy')# #TimeFormat(data.EXPIRATIONDATE)#") />
then in CouponForm.cfm you would change the date format strings in the setDateRenderer function and the dataValidator function to use the format 'm/d/Y h:i A' instead of 'm/d/Y'.
For a complete list of format characters read this page:
http://extjs.com/deploy/ext/docs/output/Date.html
<cfif trim(data.EXPIRATIONDATE) neq "">
<cfset QuerySetCell(qryFinalQuery, "EXPIRATIONDATE", "#DateFormat(data.EXPIRATIONDATE,'mm-dd-yyyy')# #TimeFormat(data.EXPIRATIONDATE)#") />
<cfelse>
<cfset QuerySetCell(qryFinalQuery, "EXPIRATIONDATE", "") />
</cfif>
Here's another post of yours that shows the same behavior..
http://www.coldfusionguy.com/ColdFusion/blog/index...
There is an attribute called "select" in the cfgridcolumn tag, the default is "Yes". If you set it to "No", the field cannot be selected or edited. So I updated the sponsorid and coupon columns to be like this:
<cfgridcolumn name="SPONSORID" header="Sponsor" width="100" select="No"/>
<cfgridcolumn name="COUPON" header="Coupon" width="100" select="No"/>
It doesn't sort the date column properly. It sorts by the first character of the date value like the following.
01/20/2008
02/20/2007
03//20/2007
I tried to cast from cfquery in my function and custom render
by using Ext.util.Format.dateRenderer but no luck. Is there something that I am totally missing here?
Thanks in advance for anyone who figured this out.
I found a way that can bypass the date formatting by queryConvertForGrid(), and also can use Ext.util.Format.dateRenderer()
in MS-SQL:
SELECT convert(varchar,NM.createDate, 1) + ' ' + convert(varchar,NM.createDate, 108) AS Date
FROM XXX
Since this is the format that Ext1.0 date object takes, (see: http://extjs.com/deploy/ext-1.0.1/docs/output/Date... ), we can use Ext.util.Format.dateRenderer() without any additional JS!!!
Below is a more complete example:
<cfsavecontent variable="head">
<script src="/CFIDE/scripts/ajax/ext/package/date.js" type="text/javascript"></script>
<script type="text/javascript">
applyCustomRenderer = function() {
grid = ColdFusion.Grid.getGridObject('newMeterGrid');
cm = grid.getColumnModel();
cm.setRenderer(0, Ext.util.Format.dateRenderer('Y-m-d'));
// 0 is the first cfgridcolumn
grid.reconfigure(grid.getDataSource(), cm);
};
</script>
</cfsavecontent>
<cfhtmlhead text="#head#">
<cfset ajaxOnLoad("applyCustomRenderer")>
- Dave
In the CFC you would add an Argument like:
<cffunction name="getCoupons" access="remote" returnType="struct">
<cfargument name="page" type="numeric" required="yes">
<cfargument name="pageSize" type="numeric" required="yes">
<cfargument name="gridsortcolumn" type="string" required="no" default="">
<cfargument name="gridsortdir" type="string" required="no" default="">
<cfargument name="SponsorID" type="numeric" required="yes">...
then in the bind expression on the CFGrid you would just add that parameter like:
<cfgrid name="CouponsGrid"
format="html"
pagesize="10"
striperows="yes"
selectmode="edit"
bind="cfc:coupons.getCoupons({cfgridpage},{cfgridpagesize},{cfgridsortcolumn},{cfgridsortdirection},#session.SponsorID#)"
onchange="cfc:coupons.editCoupon({cfgridaction},{cfgridrow},{cfgridchanged})"...
How you enter the data in the bind expression depends on your page and where the data is comming from. If you wanted to get the value in the selected node of a cftree, it would look something like:
bind="cfc:coupons.getCoupons({cfgridpage},{cfgridpagesize},{cfgridsortcolumn},{cfgridsortdirection},{SponsorTree.node})"
Then in the "data" query you would use:
<cfquery name="data" datasource="#THIS.dsn#">
SELECT COUPONID, SPONSORID, COUPON, EXPIRATIONDATE
FROM COUPONS
WHERE SponsorID = #arguments.sponsorid#
</cfquery>
There are many things that could cause it to run slow, I would start by using firebug (http://www.coldfusionguy.com/ColdFusion/blog/index...)
to monitor all the ajax requests that are occurring in the background and see if you can spot which process is slowing things down and go from there.
try navigating to
http://<yourdomainhere>/CFIDE/scripts/ajax/r...
and see if the loading.gif shows up. If it doesn't then you need to map the virtual directory to your cfide folder. None of the Ajax stuff will work without this mapping set up correctly.
In the one that is not working, are you certain that in the cfc function that you have bound to the grid is looping through the data results and rebuilding the query so that the date column is set to the varchar datatype and the date is formatted properly? Here is the portion of my cfc example above that does this:
<cfset qryFinalQuery = QueryNew("COUPONID, SPONSORID, COUPON, EXPIRATIONDATE","varchar,varchar,varchar,varchar") />
<cfloop query="data">
<cfset QueryAddRow(qryFinalQuery) />
<cfset QuerySetCell(qryFinalQuery, "COUPONID", "#data.COUPONID#") />
<cfset QuerySetCell(qryFinalQuery, "SPONSORID", "#data.SPONSORID#") />
<cfset QuerySetCell(qryFinalQuery, "COUPON", "#data.COUPON#") />
<cfset QuerySetCell(qryFinalQuery, "EXPIRATIONDATE", "#DateFormat(data.EXPIRATIONDATE, 'mm/dd/yyyy')#") />
</cfloop>
I did loop through the query and set everything to varchar for type. I don't have any dates to worry with. The plot thickens further. The fourth column is where the NaN/NaN/NaN dispalys regardless of datatype, field, etc. I can cut out however many columns I want and the NaN/NaN/NaN is what shows in the fourth column. I have corrected this for now by creating a cfgridcolumn in the fourth position and setting display="no". I have attached my grid and cfc code below for reference. HELP!!! And thanks.
CFGRID:
<cfgrid name="phoneresults" colheadertextcolor="##000099" format="html" height="270" pagesize="10" striperows="yes" selectmode = "edit" bind="cfc:cfcs.lk_employee.EmployeeSearch1({cfgridpage},{cfgridpagesize},{cfgridsortcolumn},{cfgridsortdirection},'#session.LastName#','#session.Name#','#session.Phone#','#session.Cell#','#session.Pager#','#session.MailCode#')" onchange = "cfc:cfcs.lk_employee.editArtist({cfgridaction},{cfgridrow},{cfgridchanged})">
<!---<cfif getEmpInfo.recordcount GTE 1>--->
<cfgridcolumn name="LastName" header="Last Name" width="70">
<cfgridcolumn name="FirstName" header="First Name" width="70">
<cfgridcolumn name="Middle" header="M." width="20">
<cfgridcolumn name="EmpID" display="no">
<cfgridcolumn name="Nickname" header="Alias" >
<cfgridcolumn name="Phone" header="Phone"width="60">
<cfgridcolumn name="Pager" header="Pager" width="48">
<cfgridcolumn name="Cell" header="Cell" width="80">
<cfgridcolumn name="Home" header="Emergency Phone" width="80">
<cfgridcolumn name="OtherPhone" header="Emergency Phone" width="80">
<cfgridcolumn name="Building" header="Locale" width="200" select="yes" values="#LocaleIDList#" valuesdisplay="#LocaleList#">
<cfgridcolumn name="SubDeptNum" header="Dept." select="yes" values="#DeptIDList#" valuesdisplay="#DeptNameList#" width="50">
<cfgridcolumn name="MailCode" header="MC" width="40" select="yes" values="#MCList#" valuesdisplay="#MCList#">
<cfgridcolumn name="emailadd" header="E-Mail" href="mailto:" hrefkey="emailadd" width="180">
<cfgridcolumn name="uupic" header="UUPIC" width="70">
<cfgridcolumn name="UserName" header="U Name" width="60">
</cfgrid>
CFC:
<cffunction name="EmployeeSearch1" output="false" access="remote" returntype="any" >
<cfargument name="page"
required="yes">
<cfargument name="pageSize"
required="yes">
<cfargument name="gridsortcolumn"
required="yes">
<cfargument name="gridsortdirection"
required="yes">
<cfargument name="LastName"
type="string"
required="no"
default="">
<cfargument name="Name"
type="string"
required="no"
default="">
<cfargument name="Phone"
type="string"
required="no"
default="">
<cfargument name="Pager"
type="string"
required="no"
default="">
<cfargument name="Cell"
type="string"
required="no"
default="">
<cfargument name="MailCode" type="string" required="no" default="">
<cfquery name="getEmpInfo" datasource="cfInternal">
SELECT
EmpID, LastName, FirstName, Middle, Nickname, Phone, Pager, Cell, Home, OtherPhone, emailadd, BldgNum, RoomNumber, Room, RoomID, MailCode, Office, DeptSub, Affiliation, Position, Image, Contact, Gone, Pending, EditBY, EditDate, SubID, SubDeptNum, OfficeDept, BldgID, Area, 'Bldg.'+Building+', Rm.'+RoomNumber AS Building, AreaArea,EmpIDEditBy, LastEditBy, FirstEditBy, MiddleEditBy, NickEditBy, SubDept, Title, DeptName, NewContract, AreaID, Contract, ContractName, Abbr, PosTitle, OrderofPosition, DualAssign, SubPosition, DeptCategory, UserName, RoomID, OfficeID,uupic
FROM VIEW_WSTFDirectory
WHERE Gone <> 1 and EmpID <> 681
<cfif #Arguments.LastName# NEQ ''>
and LastName LIKE <cfqueryparam value='#Arguments.LastName#%' cfsqltype='cf_sql_varchar' >
</cfif>
<cfif Arguments.Name NEQ '' >
and (FirstName LIKE <cfqueryparam value='#Arguments.Name#%' cfsqltype='cf_sql_varchar'>
or Middle LIKE <cfqueryparam value='#Arguments.Name#%' cfsqltype='cf_sql_varchar'>
or Nickname LIKE <cfqueryparam value='#Arguments.Name#%' cfsqltype='cf_sql_varchar'>)
</cfif>
<cfif Arguments.Phone NEQ ''>
and Phone LIKE <cfqueryparam value='%#Arguments.Phone#%' cfsqltype='cf_sql_varchar'>
</cfif>
<cfif Arguments.Pager NEQ ''>
and Pager LIKE <cfqueryparam value='%#Arguments.Pager#%' cfsqltype='cf_sql_varchar'>
</cfif>
<cfif Arguments.Cell NEQ ''>
and Cell LIKE <cfqueryparam value='%#Arguments.Cell#%' cfsqltype='cf_sql_varchar'>
</cfif>
<cfif Arguments.MailCode NEQ ''>
and MailCode = <cfqueryparam value='#Arguments.MailCode#' cfsqltype='cf_sql_varchar'>
</cfif>
<cfif gridsortcolumn neq ''>
order by #gridsortcolumn# #gridsortdirection#
<cfelse>
Order by LastName, FirstName, Middle
</cfif>
</cfquery>
<cfset qryFinalQuery = QueryNew("EmpID,LastName,FirstName,Middle,Nickname,Phone,Pager,Cell,Home,OtherPhone,Building,SubDeptNum,emailadd,uupic,UserName","integer,varchar,varchar,varchar,varchar,varchar,varchar,varchar,varchar,varchar,varchar,varchar,varchar,varchar,varchar") />
<cfloop query="getEmpInfo">
<cfset QueryAddRow(qryFinalQuery) />
<cfset QuerySetCell(qryFinalQuery, "EmpID", "#getEmpInfo.EmpID#") />
<cfset QuerySetCell(qryFinalQuery, "LastName", "#getEmpInfo.LastName#") />
<cfset QuerySetCell(qryFinalQuery, "FirstName", "#getEmpInfo.FirstName#") />
<cfset QuerySetCell(qryFinalQuery, "Middle", "#getEmpInfo.Middle#") />
<cfset QuerySetCell(qryFinalQuery, "Nickname", "#getEmpInfo.Nickname#") />
<cfset QuerySetCell(qryFinalQuery, "Phone", "#getEmpInfo.Phone#") />
<cfset QuerySetCell(qryFinalQuery, "Pager", "#getEmpInfo.Pager#") />
<cfset QuerySetCell(qryFinalQuery, "Cell", "#getEmpInfo.Cell#") />
<cfset QuerySetCell(qryFinalQuery, "Home", "#getEmpInfo.Home#") />
<cfset QuerySetCell(qryFinalQuery, "OtherPhone", "#getEmpInfo.OtherPhone#") />
<cfset QuerySetCell(qryFinalQuery, "Building", "#getEmpInfo.Building#") />
<cfset QuerySetCell(qryFinalQuery, "SubDeptNum", "#getEmpInfo.SubDeptNum#") />
<cfset QuerySetCell(qryFinalQuery, "emailadd", "#getEmpInfo.emailadd#") />
<cfset QuerySetCell(qryFinalQuery, "uupic", "#getEmpInfo.uupic#") />
<cfset QuerySetCell(qryFinalQuery, "UserName", "#getEmpInfo.UserName#") />
</cfloop>
<!--- And return it as a grid structure --->
<cfreturn QueryConvertForGrid(qryFinalQuery,page,pagesize)>
</cffunction>
The data you are displaying in this grid doesn't have a date field so you don't need the javascript that adds the custom renderer, just remove the script and the ajaxonload() code and the error will go away.
You also don't need that loop to convert the date column into the varchar datatype because none of the column are dates in your CFC.
I knew it had to be something simple. That did it. Thanks for this and all of your help. You have been a God Send.
http://developer.yahoo.com/yui/examples/treeview/t...
You will have to read the documentation here:
http://developer.yahoo.com/yui/docs/YAHOO.widget.T...
You can also use functions mentioned on that page to add, and remove nodes. I personally would recommend that you bypass the cftree tag and just use the YAHOO.widget.TreeView object directly for this project if you know javascript well enough to do that.
When I have some free time I will look into seeing if there is an easy way to use the cftree tag as a base and write an javascript function that will reformat the cftree and add the listeners that would be needed to do what you want, but unfortunately I just don't have the time right now to get that deep into it.
<cfif ARGUMENTS.gridsortcolumn NEQ ""
and ARGUMENTS.gridsortdir NEQ "">
ORDER BY #ARGUMENTS.gridsortcolumn# #ARGUMENTS.gridsortdir#
</cfif>
Now the Sponsor and Exp Date columns sort but when I click the Coupon column I get an error invoking the CFC... Error Executing the Database Query.
I am new to CF. Can you help?
Here is an example of the cftry/cfcatch block you should use:
<cftry>
<!--- put your cfquery here --->
<cfcatch type="any">
<cfdump var="#cfcatch#">
</cfcatch>
</cftry>
<!--- Get data --->
<cftry>
<!--- put your cfquery here --->
<cfquery name="data" datasource="#THIS.dsn#">
SELECT COUPONID, SPONSORID, COUPON, EXPIRATIONDATE
FROM COUPONS
<cfif ARGUMENTS.gridsortcolumn NEQ ""
and ARGUMENTS.gridsortdir NEQ "">
ORDER BY #ARGUMENTS.gridsortcolumn# #ARGUMENTS.gridsortdir#
</cfif>
</cfquery>
<cfcatch type="any">
<cfdump var="#cfcatch#">
</cfcatch>
</cftry>
And the error message did change but still seems cryptic to me:
Error invoking CFC cfexercises/coupons.cfc: Attribute validation error for tag cfloop.
On another note, I took your example, along with the modified SQL code, and changed it to show a list of projects I had stored in a database and it works amazing. The date is formatted correctly and all the columns are sorting as they should.
Thanks so much for taking the time to respond.
setDateRenderer = function() and the <cfset ajaxOnLoad("setDateRenderer")>
match but to no avail. Any ideas appreciated.
are you on CF9 or CF8? This example was written in CF8 and they changed a lot of stuff in terms of how they implemented Ext in CF9, so some of my hacks like this don't work in CF9.