I'm using <cfspreadsheet> to output a database query to an Excel spreadsheet. When these spreadsheets are created manually it is possible to highlight all the cells and 'Format as table'. This means when the table header is clicked, the user can sort the table ascending and descending.
Is it possible to specify this formatting in the ColdFusion code when generating the Excel file?
If you are on Coldfusion 9, you can use SpreadSheetNew, then SpreadSheetAddRow, SpreadSheetFormat functions to style an excel spreadsheet from a data set
<cfset sObj = spreadsheetNew("myreport","yes")>
<cfset SpreadsheetAddRow(sObj, "Column_1, ... , Column27")>
<cfset SpreadsheetFormatRow(sObj, {bold=TRUE, alignment="center"}, 1)>
<cfset spreadsheetAddRows(sObj, qMyQuery)>
<cfheader name="content-disposition" value="attachment; filename=report_#Dateformat(NOW(),"MMDDYYYY")#.xlsx">
BE WARNED however, this can be extremely taxing to the JVM, I had a query i was creating an xls with, applying only two styles (bold, text-center) to the header row, and any query over 700 rows would shut down the entire server via JVM memory loss...here is my SO question about it, with related code/answer SpreadsheetAddRows failing on moderate size query
It has been documented with adobe as being a bug
Related
I am moving one of our applications from ColdFusion 9.01 to ColdFusion 11 and encountered a situation where I cannot get the date formatted the way I want it using "DateFormat". I read through the docs since things have changed in CF versions, but I honestly can't figure out why this isn't working. It worked beautifully in CF 9. I know it's probably something very easy, but I am just not seeing it.
The query (Oracle DB) provides me a list of the last 30 days and the loop is simply to reformat the date output from "2014-07-01 00:00:00.0" to a more friendly looking display of 01-Jul-2014 except that I cannot get it to format as "dd-mmm-yyyy" it just spits back the original output from the query. I hard coded the date where normally there would be a cfquerparam. Any ideas?
<cfquery name="qryDateArray" datasource="#request.db#">
select trunc(to_date('07/01/2014', 'mm/dd/yyyy') + 1 - rownum) as ref_date
from dual connect by rownum <= 30
</cfquery>
<cfloop from="1" to="#qryDateArray.recordcount#" index="j">
<cfset qryDateArray.ref_date[j] = DateFormat(qryDateArray.ref_date[j], "dd-mmm-yyyy")>
</cfloop>
<cfoutput>
<cfdump var="#qryDateArray#">
</cfoutput>
I could not test this on CF11 since I do not have it handy. I did verify that your code though returns results as you explained when I ran it on my CF10 environment here. So what you can do is add a column to the query object and define it as a varchar and add your formatted data to that. This in turn dumped out the formatted dates.
<cfquery name="qryDateArray" datasource="#request.db#">
select trunc(to_date('07/01/2014', 'mm/dd/yyyy') + 1 - rownum) as ref_date
from dual connect by rownum <= 30
</cfquery>
<cfset aryData = [] />
<cfloop from="1" to="#qryDateArray.recordcount#" index="j">
<cfset ArrayAppend(aryData, DateFormat(qryDateArray.ref_date[j], "dd-mmm-yyyy")) />
</cfloop>
<cfset QueryAddColumn(qryDateArray, "STRDATE", "VarChar", aryData) />
<cfoutput>
<cfdump var="#qryDateArray#">
</cfoutput>
If dependent on the query column names then could use something like Ben's method explained here to do some renaming of the columns: http://www.bennadel.com/blog/357-ask-ben-changing-coldfusion-query-column-names.htm
It'd be great if you'd given us a portable test case rather than one that relies on your database, but I suspect it is because ColdFusion has become more rigid with its type management of query columns.
So CF considers your ref_date column to be of type date, so when you try to put the formatted string back into the query column, CF tries (and succeeds) to convert the string back into a date.
Aside:
I have to wonder why you don't format the data string in the DB from the outset, and just return it the way you need it, rather than returning something else, then looping over the thing to adjust it..?
I have a report stored as an .cfm file. I have been able to retrieve it fine with a cffile read. Now I want the option of retrieving only part of the report, say the first 50 lines. I decided to try a fileReadLine():
<cfset repname = url['rep']>
<cfset type = url['type']>
<cfset dataFile = fileOpen("/var/www/reports/moxrep/#repname#.cfm", "read" ) >
<cfset i = 0>
<cfoutput>
<cfloop
condition = "NOT FileIsEOF(dataFile) AND i LT 100">
<cfset i = i + 1>
<cfset inf = fileReadLine( dataFile ) >
#inf#
</cfloop>
</cfoutput>
<cfset fileClose( dataFile ) >
It did not retrieve things at all correctly. The formatting was messed up. All the dynamic data in the report was missing. The CSS links did not operate. And there were many extra blank lines.
Am I doing something wrong? Or is fileReadLine just not meant for retrieving a formatted report? And if not, is there any way to retrieve just part of the report with cffile?
Use cfhttp to get the report, then take that result and strip it down to what you need.
I am not sure you realize that the FileOpen() function is actually reading the raw CFML template and not actually executing the queries that populate your report. Using the CFHTTP tag is definitely a better approach, but be careful because your rendered page is likely to contain all your CSS that would be necessary for the report to render properly so use View Source on your report to see if you want just 50 lines.
The question burning in my mind is "why" do you want just 50 lines? are you previewing the report? it is only 1 page long? Are you embedding it into a dashboard? You may want to consider modifying the .cfm "report" so that the area you want to display elsewhere is wrapped with a specific tag (such as a Span or even something custom) and then when you fetch the report using CFHTTP, then you can parse the results with XMLParse() function (assuming it is properly formatted) and render the section of the report you actually want.
i am using the following code to handle the insets: but it is taking too much time, how can i enhance it or make in work faster:
<cfspreadsheet action="read" excludeheaderrow="yes" headerrow="1" rows="2-65536" query="mySheet" src="#dest#\#newfile#" sheet="1">
<cftry>
<cfquery datasource="#request.dsn#" name="myInsert">
<cfoutput>
<cfloop query="mySheet">
INSERT INTO mytable(memberID,lastname,firstname,pid,pname,gender,dob,workphone,homephone,address1,address2,city,county,state,zip,marketValue)
values('#mySheet.mem_id##RandRange(1,100)#','#mySheet.MEM_LAST_NAME#','#mySheet.MEM_FIRST_NAME#','#mySheet.PCP_ID#','#mySheet.pcp_name#','#mySheet.gender#','#mySheet.dob#','#mySheet.WORKTELEPHONENUMBER#','#mySheet.HOMETELEPHONENUMBER#','#mySheet.ADDRESSLINE1#','#mySheet.ADDRESSLINE2#','#mySheet.CITY#','#mySheet.COUNTY#','#mySheet.state#','#mySheet.zip#','#mySheet.GROUPNAME#')
</cfloop>
</cfoutput>
</cfquery>
records in excel sheet are more than 50000
Here's one way to handle it:
Put the entire thing (cfspreadsheet and all inserts) into a threaded function so it can run in the background
Save the spreadsheet query (upload results) into a session variable
After the upload is complete, do your insert queries in batches; in each batch add any issues into a feedback array that is also in the session scope
On the user's end, have a page that tells the user that things are "Working..." and re-loads and displays the feedback issues as they come up (best to do with ajax)
The code below will generate the file with two notable errors:
The SpreadSheetSetColumnWidth appears to stop working after 21 lines on the second tab
The image does not appear on the second tab
Hopefully this is just a rookie mistake and not something along the lines of the date formatting issue. Any help or constructive criticism to fix/improve would be greatly welcomed.
<!--- We cannot directly reference the image --->
<cfimage source="img/A2LA_Logo.jpg" name="A2LA_Logo">
<!--- We need an absolute path, so get the current directory path. --->
<cfset theFile=GetDirectoryFromPath(GetCurrentTemplatePath()) & "Final_Report.xlsx">
<cfscript>
/*********************************************************************** Page 1 ***********************************************************************/
//Create a new Excel spreadsheet object - SpreadsheetNew("Tab name", "yes=2007+ no=2003");
Tab1 = SpreadsheetNew("Final Report Page 1","yes");
SpreadSheetSetRowHeight(Tab1,1,45);
SpreadsheetSetCellValue(Tab1,"Final Test Report",1,1); SpreadsheetMergeCells(Tab1,1,1,1,10);
SpreadsheetAddImage(Tab1, A2LA_Logo,"jpg","1,12,2,20");
SpreadsheetSetCellValue(Tab1,"Confidential",1,21); SpreadsheetMergeCells(Tab1,1,1,21,30);
</cfscript>
<cfscript>
/*********************************************************************** Page 2 ***********************************************************************/
//Create a new Excel spreadsheet object - SpreadsheetNew("Tab name", "yes=2007+ no=2003");
Tab2 = SpreadsheetNew("Final Report Page 2","yes");
SpreadSheetSetRowHeight(Tab2,1,45);
SpreadsheetSetCellValue(Tab2,"Final Test Report",1,1); SpreadsheetMergeCells(Tab2,1,1,1,10);
SpreadsheetAddImage(Tab1, A2LA_Logo,"jpg","1,12,2,20");
SpreadsheetSetCellValue(Tab2,"Confidential",1,21); SpreadsheetMergeCells(Tab2,1,1,21,30);
</cfscript>
<!--- There must be a better way --->
<cfscript>
for(index=1; index LTE 30; index++) {SpreadSheetSetColumnWidth(Tab1,index,3);}
for(index=1; index LTE 30; index++) {SpreadSheetSetColumnWidth(Tab2,index,3);}
</cfscript>
<!--- Write the spreadsheet to a file, replacing any existing file, then append the other tabs. --->
<cfspreadsheet action="write" filename="#theFile#" name="Tab1" overwrite=true>
<cfspreadsheet action="update" filename="#theFile#" name="Tab2">
<!--- Needs to be at the bottom --->
<cfheader name="Content-disposition" value="attachment;filename=Final_Report.xlsx">
<cfcontent type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" file="#theFile#">
<!--- Clean up your mess after it is served to the browser --->
<cffile action="delete" file="#theFile#">
(From the comments)
The images show up when saved separately. So I am guessing it is an update bug. My usual recommendation is "do not use update if you can avoid it". Create a single workbook, add multiple sheets and save it with a single write action.
Update: As for why, in your original code, you are creating two separate workbook objects. Conceptually think of it like creating two separate Excel files, with one sheet each. Then trying to merge them into one file:
c:/path/to/MyWorkbookNamedTab1.xlsx
| Sheet1 /
c:/path/to/MyWorkbookNamedTab2.xlsx
| Sheet1 /
That is different than creating a single workbook, with multiple sheets (like in the link above):
c:/path/to/MySingleWorkbook.xlsx
| Sheet1 / Sheet2 /
There is no easy way to merge separate files (or workbooks). You basically have to copy everything from one workbook into another (which is a lot more involved than it sounds). That is what action=update does. Somewhere in the copying process it loses one of the images. I am not sure if it that is due to a CF bug one in POI.
In any case, the simplest solution is to avoid the need for copying altogether. If you stick with a single workbook (and add multiple sheets to it) then there is no need for copying or merging. Everything is already contained in one workbook. So all action=write has to do is save it to disk, exactly "as is".
I have a query which I'd like to output in a spreadsheet in Excel. I'd like some of the cell columns to be formatted in a certain way, which is thousands grouping and in Number format so that sums, additions etc can be done on that row without any further alteration.
I have read through the documentation but it has left me a bit confused on how to output to Excel in the first place.
I started out with a comment but it will be easier to read as an answer.
What have you tried? Have you read through the CFSpreadsheet documentation? Should be pretty straight forward. One of the parameters to the CFSpreadsheet tag is 'query'. Why not start with that and see how it formats the columns for you by default to see what needs tweaking.
Here is an example taken directly from the referenced documentation page:
<cfquery name="courses" datasource="cfdocexamples">
SELECT CORNUMBER, DEPT_ID, COURSE_ID, CORNAME
FROM COURSELIST
</cfquery>
<cfscript>
//Use an absolute path for the files. --->
theDir=GetDirectoryFromPath(GetCurrentTemplatePath());
theFile=theDir & "courses.xls";
//Create an empty ColdFusion spreadsheet object. --->
theSheet = SpreadsheetNew("CourseData");
//Populate the object with a query. --->
SpreadsheetAddRows(theSheet,courses);
</cfscript>
<!--- Write the sheet to a file --->
<cfspreadsheet action="write" filename="#theFile#" name="theSheet" sheetname="courses" overwrite=true>
See the documentation for SpreadsheetFormatColumn, SpreadsheetFormatColumns, SpreadsheetFormatRow and SpreadsheetFormatRows to read about formatting particular cells.
You just need to use the cfspreadsheet tag to create the file, and you can format the cells with the spreadsheetFormat* functions. You can find an example of how to do this at Ray Camden's site.