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
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
I really meant
mydata.totalLength
:)
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
Thanks. After the day I had (will blog later) it is appreciated.
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!
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!
See: http://www.coldfusionjedi.c...
Thank you so much!
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
You would need to check with the plugin, not jQuery. The plugin should have it's own API.
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.
Can you be more specific? Are you saying it isn't exporting per the current sort?
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.
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.
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.
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).
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?
Search my blog for cfgrid - I know I covered this in the past 4 weeks (but a bit too busy to searhc myself).
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?
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.
Here's a pretty slick solution to export all records to Excel
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!
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.)
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;
}
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.
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;
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.
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.
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)
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?
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;
If you are using this in CF9, it probably won't work. The code used for Grids had a major update (2 Ext versions).
That was super fast. We are using Cold Fusion 8 or 8.01. We are in the process of moving to ColdFusion 9.
Thanks
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.
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.
It is possible - the only difference is in how you get the data. I think it is getStore not getDataSource() anymore.
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?
I think you're going to have problems mixing ajax and flash
Sorry - but I don't recommend using Flash Forms at all. Use Flex - sure. But not Flash Forms.
Thanks for your answer, but its doesn´t matter... its also doesn´t working with html
Can you post your latest code to Pastebin and post the URL here?
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¶meter2=wert2');
}
</script>
</cfoutput>
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.
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.
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?
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?
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.
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
Your example with <cfajaximport tags="cfwindow"> is running (without cfgrid). How can I use cfgrid in it?
Thanks again
Switch tags to cfgrid?
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?
I think you are just missing initshow in the config object. Please see the CFML Reference as it details ColdFusion.Window.create fully.
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.
Do you mean - how would you add 'export' to the grid that has the search capability too?
yes, exactly.
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. ;)
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
Woot - glad you got it. :)
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.
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.
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.
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.
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.
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...
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.