Exporting from CFGRID

A user asked me today if it was possible to export data from the new HTML-based grid in ColdFusion 8. While there is no direct support, you can roll your own. Let's take a look at one solution to this problem.

The first thing I'll do is share the base code that will power my grid. I have one cfc, test, with a getData method that looks like so:

<cffunction name="getData" access="remote" returnType="struct" output="false"> <cfargument name="page" type="numeric" required="true"> <cfargument name="size" type="numeric" required="true"> <cfargument name="sortcol" type="string" required="true"> <cfargument name="sortdir" type="string" required="true"> <cfset var q = "">

<cfquery name="q" datasource="cfartgallery"> select * from art <cfif len(arguments.sortcol)> order by #arguments.sortcol# <cfif len(arguments.sortdir)> #arguments.sortdir# </cfif> </cfif> </cfquery>

<cfreturn queryConvertForGrid(q, arguments.page, arguments.size)> </cffunction>

There isn't anything special about this method - except for the fact that it was specifically built to handle CFGRID ajax calls. Note the 4 attributes and the queryConvertForGrid used in the return. Now let's take a look at my form:

<cfform name="myform"> <cfgrid name="reportsGrid" format="html" pageSize="10" stripeRows="true" bind="cfc:test.getData({cfgridpage},{cfgridpagesize},{cfgridsortcolumn},{cfgridsortdirection})" > <cfgridcolumn name="artname" header="Name"> <cfgridcolumn name="price"> </cfgrid> <cfinput type="button" name="Download" value="Download" onClick="doDownload()"> </cfform>

Ok - so far nothing special, but I did add a download button that runs a JavaScript function named doDownload.

At this point I figured out what solution I would use. I would simply look at the grid - get the current sort column, sort direction, and page, and make a call to a page that would run the same call my grid was. Turns out though that was harder said then done. Believe it or not, there was no API for the Grid (over at Ext's docs) that specifically returned the current sort or page. My buddy Todd Sharp did some digging and found that properties existed for these values, you just had to digg a bit. So let's take a look at the complete function and I'll explain what each line is doing.

<script> function doDownload() { var mygrid = ColdFusion.Grid.getGridObject('reportsGrid'); var mydata = mygrid.getDataSource(); var params = mydata.lastOptions.params; var sort = params.sort; var dir = params.dir; page = params.start/params.limit+1; document.location.href='download.cfm?page='+page+'&sort='+sort+'&dir='+dir+'&size='+params.limit; } </script>

The first thing I do is get the Ext grid object using the ColdFusion.Grid.getGridObject function. This is documented in the CF Reference. I then get the DataSource object for the grid. What is that? I'm not quite sure. The API docs mention the function, but don't fully explain what the DataSource is. Todd found that within this object there was a 'lastOptions.params' key that stored all the values I needed. This includes the sort and dir (although the values are undefined if you don't click anything), and a start and limit value that lets me get the current page. At this point I have everything ColdFusion had when it made its Ajax request. So I just duplicate it a bit and push the user to download.cfm. Here is the code for that page:

<cfparam name="url.page" default="1"> <cfparam name="url.limit" default="10"> <cfparam name="url.sort" default=""> <cfparam name="url.dir" default="">

<cfinvoke component="test" method="getData" page="#url.page#" size="#url.size#" sortcol="#url.sort#" sortdir="#url.dir#" returnVariable="result">

<cfheader name="Content-Disposition" value="inline; filename=download.pdf"> <cfdocument format="pdf"> <table> <tr> <th>Name</th> <th>Price</th> </tr> <cfoutput query="result.query"> <tr> <td>#artname#</td> <td>#price#</td> </tr> </cfoutput> </table> </cfdocument>

All I've done here is hit the same CFC my grid was hitting. I output the result within cfdocument to create a simple page.

Archived Comments

Comment 1 by doug cain posted on 9/5/2007 at 10:54 AM

Cool, looks like you should be able to use this technique to get other information from the grid like total row count which would be handy for display to the user.

Just had a look and it's there:

params.totalLength

Comment 2 by doug cain posted on 9/5/2007 at 11:07 AM

I really meant

mydata.totalLength

:)

Comment 3 by Michael White posted on 9/5/2007 at 6:07 PM

This is great stuff, Ray. I got you something small off your wishlist, not for anything in particular, just as a token of support for all you've done to help everyone with CF8

Comment 4 by Raymond Camden posted on 9/6/2007 at 3:47 AM

Thanks. After the day I had (will blog later) it is appreciated.

Comment 5 by Azadi Saryev posted on 10/1/2007 at 2:32 PM

is it possible to access these properties, and the totalLength mentioned by doug, using a ajaxonload() function or other similar way? i.e. without having to click a button?
i have tried to use <cfset ajaxonload('myfunction')>, but it always returns 'undefined' for totalLength... it seems to fire before the grid is fully loaded, since my test alert pop up over an empty grid shell...

also, any ideas on how to re-render a grid on the client's side only, but with different default options for, say, pageSize? that would allow to add a custom 'Show X records per page' control, without hitting the db with every change in X...

Thanks!

Comment 6 by Neil Poso posted on 10/6/2007 at 4:32 AM

how do i get the values of 'name' and 'price' in a selected row using a javascript?

like for instance on your doDownload(), instead of generating an exported pdf data, i just would like to get the selected value of 'name' and 'price' and of course the 'id' of the row.

thanks a lot!

Comment 7 by Raymond Camden posted on 10/7/2007 at 11:43 PM
Comment 8 by Neil Poso posted on 10/8/2007 at 8:28 PM

Thank you so much!

Comment 9 by Jim posted on 3/9/2009 at 7:13 PM

Ray,

Do you know if there is a similar call in jquery like the CF call [ColdFusion.Grid.getGridObject]? We are using a jquery plugin (jqgrid) and need to dump the query to pdf or excel.

thanks, Jim

Comment 10 by Raymond Camden posted on 3/9/2009 at 7:15 PM

You would need to check with the plugin, not jQuery. The plugin should have it's own API.

Comment 11 by josh posted on 4/8/2009 at 10:35 PM

Hi Ray, is there a way to use this method and print all records in the grid but with the sort parameters specified by the user? It seems like there should be but I can't figure it out.

Comment 12 by Raymond Camden posted on 4/8/2009 at 10:37 PM

Can you be more specific? Are you saying it isn't exporting per the current sort?

Comment 13 by josh posted on 4/9/2009 at 8:13 PM

it's exporting, but only the current page. I want all the pages returned and displayed in a single document, not just the current page.

Comment 14 by Raymond Camden posted on 4/9/2009 at 8:14 PM

Then you have to modify my code. My code gets the page and sort and runs a CFM. Your code would not worry about the page. Should be easy enough.

Comment 15 by josh posted on 4/9/2009 at 9:16 PM

I guess what I'm trying to get at, and I'm sorry I'm kind of new at this, is that since the page and pagesize are required arguments, how do I specify them so that they show all. I can get it to display more records just by bumping up the default numbers for page and pagesize but I can't get it to just show all of them. thanks again.

Comment 16 by Raymond Camden posted on 4/10/2009 at 5:54 PM

I may not be getting you still. My code gets the current page and dir from the grid, and sends it to a new CFM. THe last script block on the blog post. All you have to do is NOT use the page and size values. If your method, getData(), requires it, simply modify it to make them optional, or, worse comes to worse, if getData() is used to power the grid, make a new method, getAll() (but that's really poor design though).

Comment 17 by Sanjeev posted on 4/23/2009 at 12:41 AM

The mydata.totalLength gives the length of the grid records size/page.

How do i know if the grid is populated or not..in the sense, if the Query returned records or not?

Comment 18 by Raymond Camden posted on 4/23/2009 at 12:42 AM

Search my blog for cfgrid - I know I covered this in the past 4 weeks (but a bit too busy to searhc myself).

Comment 19 by futr_vision posted on 6/22/2009 at 7:28 PM

Is there a way to export cfgrid to Excel either by hard coding or using a plugin or is there a better way to get the data into Excel such as taking the data directly from the DB via the query?

Comment 20 by Raymond Camden posted on 6/22/2009 at 11:28 PM

You would just modify my code to output an HTML table. That's the cheesy/simple way to do it. You have to modify the cfcontent/cfheader tag, but that's really it.

Comment 21 by futr_vision posted on 6/24/2009 at 11:47 PM

Here's a pretty slick solution to export all records to Excel

Comment 22 by Ed posted on 9/10/2009 at 9:02 PM

This export from grid example is great. I can export the current page of records to excel just fine. However, I am struggling with the option in doDownload() that exports the current grid page. I have multiple pages in the grid and need to export all records to the excel document. Can you help me understand the page variable you use? Thanks for all your great examples!

Comment 23 by Raymond Camden posted on 9/10/2009 at 9:04 PM

Add a new button that calls a new function. Have it run a CFM, just like the other function, but this one doesn't bother getting the page number. It could - if it wants, get the sort and dir. Then your CFM code pretty much just mimics the other code - but with no pagination. (To be honest, I'd use one CFM that made the paging optional.)

Comment 24 by Ed posted on 9/11/2009 at 9:28 PM

Hi Ray, well I have solved the export total grid data problem. I just added a line to the doDownload() function to get the total of the getDataSource. This gets the total limit to send to the download excel file. The problem I have now is, I am using a filtercolumn and filter text field to generate the dynamic grid. When I filter for data and then download to excel, i get the correct number of rows , but not the correct data. I am not passing the filtercolumn or filter string to the CFC. So I am getting the original defalut data from onload. Any idea how to pass the filtercolumn in the doDownload() function to my CFC?
Updated code:
function doDownloadAll() {
var mygrid = ColdFusion.Grid.getGridObject('reportsGrid');
var mydata = mygrid.getDataSource();
var params = mydata.lastOptions.params;
var sort = params.sort;
var dir = params.dir;
var page = params.start+1;
var size = mygrid.getDataSource().totalLength;
document.location.href='download2.cfm?page='+page+'&sort='+sort+'&dir='+dir+'&size='+size;
}

Comment 25 by Raymond Camden posted on 9/12/2009 at 12:17 AM

You would need to get it and add it to the URL in the document.location.href. As for how you get the filter, it depends on how you implemented it. Is it just a form field? Then you use normal JS type DOM ops to get the form value.

Comment 26 by Ed posted on 9/12/2009 at 12:52 AM

YESSSSS!!! It works perfectly now. Now I will be able to enjoy my weekend. Thank you.
Update:
document.location.href='download2.cfm?page='+page+'&sort='+sort+'&dir='+dir+'&size='+size+'&filtercolumn='+filtercolumn+'&filter='+filter;

Comment 27 by josh posted on 9/26/2009 at 12:07 AM

hi ray, i've gotten this working fairly well but my report is not sorting properly. The sortcol and sortdir are getting passed properly, as I can view them in the url but the sort is not being applied. I've got other filterparams that do pass and apply properly, just not the sort and dir. Does the sort work in your example? I can't see a meaningful difference in what i've done vs what is presented.

Comment 28 by Raymond Camden posted on 9/26/2009 at 12:09 AM

If you know the sort and sortdir URL variables are passed, then check your CF code to ensure it is actually _using_ it properly. You can add some simple <cflog> lines to test, or a cfdump too.

Comment 29 by Alan posted on 2/16/2010 at 9:52 PM

Hello,

For some reason the following line of code sets the variable params to "undefined",

var params = mydata.lastOptions.params;

The code you suggested prior to that line works just fine. I have a cfgrid and I just want to know if the user sorted the list by some column before exporting to Excel (I want to preserve that sorted cfgrid list in Excel)

Comment 30 by Alan posted on 2/17/2010 at 3:01 AM

Actually it turns out that you must use a bind for the cfgrid for it to work. Is there anyway I can use the "query" attribute instead and still be able to get the sort column?

Comment 31 by Willene posted on 2/26/2010 at 3:21 AM

When trying to use this code the error on page message comes up which gives the message
"lastOptions.params; " is null or not an object

The code for mygrid = ColdFusion.Grid.getGridObject('InOutStatus'); is used in another function - would that be a cause. When searching on google, this page is the main result. Thanks for your help.
var mydatad = mygridd.getDataSource().totalLength;

Comment 32 by Raymond Camden posted on 2/26/2010 at 3:22 AM

If you are using this in CF9, it probably won't work. The code used for Grids had a major update (2 Ext versions).

Comment 33 by Willene posted on 2/26/2010 at 3:37 AM

That was super fast. We are using Cold Fusion 8 or 8.01. We are in the process of moving to ColdFusion 9.

Thanks

Comment 34 by Raymond Camden posted on 2/26/2010 at 3:42 AM

Hmm. So I can't see why getting the grid _elsewhere_ would be a bug, but if you have code on the page that isn't my code, I'd try removing it (just for now) and seeing if that is the issue. Not that the bug would be your fault, or mine, but that maybe there is a conflict between the two.

Comment 35 by Nik posted on 5/20/2010 at 8:35 AM

Do you know how to do this in CF9? I am looking for something similar but wondering if its possible to do this using CF9 and cfspreadsheet.

Comment 36 by Raymond Camden posted on 5/22/2010 at 12:16 AM

It is possible - the only difference is in how you get the data. I think it is getStore not getDataSource() anymore.

Comment 37 by Morph posted on 8/19/2010 at 3:29 PM

Hi Ray,
i have a problem with my cfgrid. I want to get the result of a query over a ajax request. It means, that I push a button and the query runs in the background. Its working but not with cfgrid... I just make this code of the request Site:

<cfoutput>
<cfquery datasource="#data_source#" name="test">
#PreserveSingleQuotes(test)#
</cfquery>
</cfoutput>

<cfform>
<cfgrid name = "FirstGrid" format="Flash"
height="320" width="1200"
font="Tahoma" fontsize="12"
query = "test">
</cfgrid>
</cfform>

Can you get me an answer?

Comment 38 by Michael White posted on 8/19/2010 at 3:34 PM

I think you're going to have problems mixing ajax and flash

Comment 39 by Raymond Camden posted on 8/19/2010 at 3:51 PM

Sorry - but I don't recommend using Flash Forms at all. Use Flex - sure. But not Flash Forms.

Comment 40 by Morph posted on 8/19/2010 at 4:59 PM

Thanks for your answer, but its doesn´t matter... its also doesn´t working with html

Comment 41 by Raymond Camden posted on 8/19/2010 at 5:03 PM

Can you post your latest code to Pastebin and post the URL here?

Comment 42 by Morph posted on 8/19/2010 at 5:29 PM

On the main.cfm there is a submit button which send all entries in the input fields to the
second.cfm. This is the button: (hdnr is one of the entries)

var sUrl = ("#application.pfad_root#internal/second.cfm?hdnr="+document.test.number.value + "&.......);
<button name="btnSearch" type="submit" value="startsql" onClick="startajax('divposition','',sUrl,'#ajaxpicture#');return false;">startSQL</button>
----------------------------------------------------------
second.cfm:

<cfset test = "Select......where hdnr='#hdnr#'">

<cfoutput>
<cfquery datasource="#data_source#" name="test">
#PreserveSingleQuotes(test)#
</cfquery>
</cfoutput>

<cfform>
<cfgrid name = "FirstGrid" format="html"
height="320" width="1200"
font="Tahoma" fontsize="12"
query = "test">
</cfgrid>
</cfform>
-----------------------------------------------------------
These is the function ajax

<cfoutput>
<script>
function getXmlObject() {
var http_request;
if (window.XMLHttpRequest) {
http_request = new XMLHttpRequest();
} else {
if (window.ActiveXObject) {
http_request = new ActiveXObject("Microsoft.XMLHTTP");
}
}
return http_request;
}

function startajax(sDiv, sText, sAufruf, sBild) {
document.getElementById(sDiv).innerHTML = '<span class="Stil6"><img src="' + sBild + '" /> ' + sText + '</span>';
var http_request_dat = getXmlObject();
http_request_dat.onreadystatechange = function() {
try {
if (http_request_dat.readyState == 4) {
if (http_request_dat.status == 200) {
document.getElementById(sDiv).innerHTML = http_request_dat.responseText;
}
}
return true;
} catch(e) {
//nichts
}
};
http_request_dat.open("POST", "#application.http_pfad#" + sAufruf + "#pageVar.URLDefaultParam#");
http_request_dat.setRequestHeader('Content-Type','application/x-www-form-urlencoded; charset=ISO-8859-1');
http_request_dat.send('parameter=value&parameter2=wert2');
}
</script>
</cfoutput>

Comment 43 by Raymond Camden posted on 8/20/2010 at 5:20 AM

I got to say I'm totally confused. Your first page should be the grid but it should also include the JS to fire off the request to the next page. I don't see that in your first page.

Comment 44 by Morph posted on 8/20/2010 at 10:26 AM

Hi,
the ajax js is on the first page. The grid is on the second page because I want only show the grid if somebody click the button.

Comment 45 by Raymond Camden posted on 8/20/2010 at 3:42 PM

So your intended process is - page 1 they click something and it laods page 2 which shows the grid? So this isn't an grid export issue like the blog topic is about? Is it online perhaps where we can see?

Comment 46 by Morph posted on 8/20/2010 at 3:52 PM

Oh sorry for the wrong posting. Yes you are right, page1 --> click --> page2 with grid. Unfortunally, it is an Intranet. I cannot show you the page. Can you give me an adivce for my proble`m?

Comment 47 by Raymond Camden posted on 8/20/2010 at 3:59 PM

If you are using page A to load content B that contains a grid, you may run into issues because A never loaded the scripts the B needs. You can try the cfajaximport tag.

Comment 48 by Morph posted on 8/20/2010 at 4:58 PM

I don´t know how it works. I have to put the cfajaximport in page1. In page two, I have to write the cfgrid. Is that correct? What about the button?
thanking you in anticipation

Comment 49 by Morph posted on 8/20/2010 at 6:17 PM

Your example with <cfajaximport tags="cfwindow"> is running (without cfgrid). How can I use cfgrid in it?

Thanks again

Comment 50 by Raymond Camden posted on 8/20/2010 at 6:18 PM

Switch tags to cfgrid?

Comment 51 by Morph posted on 8/23/2010 at 4:03 PM

Hi, this is my mainpage:
<cfoutput>
<cfajaximport tags="cfgrid">

<a href="JavaScript:ColdFusion.Window.create('ImageWindow','Butterfly Image','page2.cfm',{width:450, height:375})">
<h3 style="color:RosyBrown">
<b>click</b>
</h3>
</a>
</cfoutput>

I know, that is not correct:
<a href="JavaScript:ColdFusion.Window.create....
But what is correct?

Comment 52 by Raymond Camden posted on 8/24/2010 at 4:04 PM

I think you are just missing initshow in the config object. Please see the CFML Reference as it details ColdFusion.Window.create fully.

Comment 53 by Ron Rattie posted on 10/19/2011 at 9:36 PM

How would you combine this with searching the cfgrid (as you demonstrated here http://www.coldfusionjedi.c... )? I'm trying, but I can't seem to pass the column and filter.

Comment 54 by Raymond Camden posted on 10/19/2011 at 9:39 PM

Do you mean - how would you add 'export' to the grid that has the search capability too?

Comment 55 by Ron Rattie posted on 10/19/2011 at 9:43 PM

yes, exactly.

Comment 56 by Raymond Camden posted on 10/19/2011 at 9:49 PM

Um, sure. It can be done. I guess I'll add this to my queue of blog entries. Wishlisting makes the queue go faster just fyi. ;)

Comment 57 by Ron Rattie posted on 10/19/2011 at 10:38 PM

I figured it out! If you have a cfselect menu named 'column1' and cfinput named 'filter1' (this is the table column and then what you are using to filter).

The JS would look like this:

var column = ColdFusion.getElementValue ('column1');
var filter = ColdFusion.getElementValue ('filter1');
var searchd = ColdFusion.getElementValue ('search1');
document.location.href='export.cfm?page='+page+'&sort='+gdsort+'&dir='+dir+'&size='+params.limit+'&column='+column+'&filter='+filter+'&search='+searchd;

Thanks Ray! Not because you gave me the answer, but because reading all your blogs has helped me learn so much about CF that I was able to figure this out on my own.

- Ron

Comment 58 by Raymond Camden posted on 10/20/2011 at 1:54 AM

Woot - glad you got it. :)

Comment 59 (In reply to #50) by Cita posted on 4/16/2015 at 8:33 PM

Hi Ray,

I'm using CF10 CFGRID. I can export my CFGRID output to PDF or Excel. However, a user is requesting that when he hides some columns on the grid, he wants only the columns shown when he exports to PDF. Is this possible? Also, where can I find the documentation for the ColdFusion.Grid.getGridObject. In CF doc, it's directing me to Ext JS library.
Thanks for any help.

Comment 60 (In reply to #59) by Raymond Camden posted on 4/17/2015 at 1:18 PM

Yes, it is possible. You would need to look at the Ext docs and find the API that returns the visible columns, and then pass this to the server side.

Docs for CF may be found here: https://wikidocs.adobe.com/...

And yes - CF will direct you to the Ext docs - because CFGRID wraps an Ext object.

Comment 61 (In reply to #60) by Cita posted on 4/19/2015 at 8:24 PM

Hi Ray,

My problem is finding the API documentation. I'm not familiar with the Ext but looked at the Ext library referred in CF doc. I also checked this folder Y:\ColdFusion10\cfusion\wwwroot\CFIDE\scripts\ajax\ext\docs\output. but not sure. Can you please direct me where to find it?
Many Thanks.

Comment 62 (In reply to #61) by Raymond Camden posted on 4/19/2015 at 11:47 PM

Well, what I would do is look at the headers of the script files. They should have a version #. Then Google for that. I don't use CF10 (I have CF11 here) but in the past, that's what I did to a) find out what version of Ext CF shipped and then find the relevant docs.

Comment 63 (In reply to #62) by Cita posted on 4/20/2015 at 4:03 PM

Thanks, Ray. CF10 is using EXT 3.1 and was able to find the API documentation; however, I was unable to find in the doc related to grid columns/header (maybe did not dig enough). Anyway, I moved my apps to CF11 since we have an existing env. There's a method called getVisibleGridColumns() and used it in my function call.

function doDownload()

var mygrid = ColdFusion.Grid.getGridObject('reportsGrid');

//added
var myheader = mygrid.header.Container.getVisibleGridColumns();
//tried
var mycols = mygrid.getVisibleGridColumns();
These 2 statements did not work. I know I'm missing something but not sure...
Any help is greatly appreciated. Thanks in advance.

Comment 64 (In reply to #63) by Raymond Camden posted on 4/20/2015 at 4:08 PM

Unfortunately I can't really help. I urge developers to not use cfgrid and other cf ui tags anymore and instead simply avoid the entire feature altogether. To be clear, I'm not saying avoid Ext, I'm saying avoid the CF UI layer. See this for more info: https://github.com/cfjedima...

Comment 65 by Stephanie Lee posted on 7/16/2015 at 7:57 PM

Since this code was originally used for development in ColdFusion 9 and we are now upgrading to ColdFusion 11, I wanted to post my altered solution to the JavaScript portion. I had to rewrite:

var reclamaGrid = ColdFusion.Grid.getGridObject('cfGridName');
var reclamaData = reclamaGrid.getStore();
var params = reclamaData.lastOptions.params;
var sort = params.sort;
var dir = params.dir;
var size = params.limit;

to

var reclamaGrid = ColdFusion.Grid.getGridObject('cfGridName');
var reclamaData = reclamaGrid.getStore();
var lastOptions = reclamaData.lastOptions;

if (lastOptions.sorters.length == 0){

var sort = "nomineeName";
var dir = "ASC";
}
else{
var sort = lastOptions.sorters[0].property;
var dir = lastOptions.sorters[0].direction;
}
var size = lastOptions.limit;

I could not find where the original sort column and direction were stored, however, so I hard coded those to what it would be if it were blank. I hope this will help someone out there struggling.