cfdocument prevent page breaks mid-row - coldfusion

I know this has been asked before but the solutions are from 2.5+ years ago so I'm asking if anyone has devised or knows of a more elegant solution to the problem using CF9. Can anyone confirm if CF10 supports the "page-break-inside: avoid" rule?
How can I prevent page-break in CFDocument from occuring in middle of content?
COLDFUSION: cfdocument and forcing a pagebreak
This is pretty much how I'm doing it. I've estimated, depending on what type of page it is, I can fit 9 or 11 rows of data before having to force a page break. Of course this is prone to breaking so if anyone knows of any evolution of the solution I would be grateful.

I believe I have found a pseudo solution. It is basically just what I said in the comments above. I take a best guess and see if it fits using the value of cfpdf's getInfo.totalPages. If it fits, great, merge it to the final document, if it doesn't, try again with one less row.
The downside to doing it this way is that it slows it down a bit and you can't use some of the stuff cfdocument makes easy like like messing with headers and footers. That being said, part 2 of this solution may be to record the number of rows that fit on a page in an array instead of merging the pages and rebuild the entire document again using cfdocument and those values as the loop constraints forcing a page break after. As it is, the below solution is already a little time consuming so building it again inside of a cfdocument tag may not work in high traffic sites.
Bug workaround: It looks like there is a bug with cfdocument that removes the background colors when saving the document to memory with the name attribute. The workaround is to remove the cfdocument tag to an external file. I saw one programmer placed it into a cfc, I found it's possible to use a simple cfinclude.
I hope someone finds this helpful, and if you know a better way to do this please comment.
<cfset reviewText = "Lorem ipsum dolor sit amet, + lots of characters.">
<cfset estimatedRowsPerPage = 7> <!--- This is the max number of records you want to try on each page. The larger the gap between max and actual will slow down the process. Used to reset attemptedRowsPerPage if the value changes --->
<cfset attemptedRowsPerPage = estimatedRowsPerPage> <!---- number of rows attempted to add to the page --->
<cfset totalRowsOutput = 0><!--- this is the number of records successfully saved to the final PDF --->
<cfset recordCount = 20> <!--- this is the query's record count --->
<!--- cfpdf cannot create a file from scratch and cfdocument requires some content so a container object cannot be created without at least one page. This page will be deleted later --->
<cfdocument format="pdf" marginbottom=".25" margintop=".25" marginleft=".25" marginright=".25" name = "finalDocument">Delete me</cfdocument>
<cfloop condition="totalRowsOutput lt recordCount">
<!--- create what *should* be a single page document --->
<cfdocument format="pdf" marginbottom=".25" margintop=".25" marginleft=".25" marginright=".25" name = "testDocument">
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>A title</title>
</head>
<body>
<table border="1">
<tr>
<td>Row:</td>
<td>Title:</td>
<td>Author:</td>
<td>Price:</td>
<td>Average Rating:</td>
<td>Reviews:</td>
</tr>
<cfoutput>
<cfloop from = "1" to = "#attemptedRowsPerPage#" index = "i">
<tr>
<td>
#i#
</td>
<td nowrap="nowrap">
#mid(reviewText,1,randRange(4,10))#
</td>
<td nowrap="nowrap">
#mid(reviewText,20,randRange(8,20))#
</td>
<td>
$10.00
</td>
<td>
#randRange(1,5)#
</td>
<td>
#mid(reviewText,1,randRange(10,700))#
</td>
</tr>
</cfloop>
</cfoutput>
</table>
</body>
</html>
</cfdocument>
<!--- get the document info to see if the page count = 1 --->
<cfpdf action="getinfo" source="testDocument" name="testInfo">
<cfif testInfo.totalPages gt 1>
<!--- if the page count is greater than 1 we need to try again with one less record. --->
<cfset attemptedRowsPerPage -= 1>
<cfelse>
<!--- merge the new single page to the final document --->
<cfpdf action = "merge" name = "finalDocument">
<cfpdfparam source="finalDocument">
<cfpdfparam source="testDocument">
</cfpdf>
<cfset totalRowsOutput += attemptedRowsPerPage>
<!--- if the page count = 1, we need to increment the startAttempt and reset the attemptedRowsPerPage unless attemptedRowsPerPage = recordCount --->
<cfif totalRowsOutput lt recordCount>
<!--- don't try to output more than exist --->
<cfset attemptedRowsPerPage = estimatedRowsPerPage+totalRowsOutput lt recordCount ? estimatedRowsPerPage : recordCount-totalRowsOutput>
</cfif>
</cfif>
</cfloop>
<!--- delete the manditory page needed to create our final document --->
<cfpdf action="deletePages" pages="1" source="finalDocument" name="finalDocument">
<!--- see "http://www.raymondcamden.com/index.cfm/2007/7/12/ColdFusion-8-Working-with-PDFs--A-Problem" to see why you need toBinary --->
<cfcontent type="application/pdf" variable="#toBinary(finalDocument)#">

Travis - I don't know of another way to do this. Usually I create an HTML table as the core of each "page" and have a specific number of rows before I close the table, break the page and open a new table.

Related

Coldfusion creating reports with wkhtmltopdf

I am trying to get some assistance with wkhtmltopdf.
I have downloaded and installed and trying to get it to work correctly with my program.
What I have is a form that lets the user choose print, preview or excel. When the user chooses print I want pop up the html table that is created by a query depending on the associates and locations that are chosen.
Right now when the user chooses print it just shows this table in the browser.
<cfif FORM.Format IS "print">
<!---<cfdocument format="pdf" scale="75" backgroundvisible="yes" overwrite="no" fontembed="yes">--->
<link rel="stylesheet" href="css/form-table.css"/>
<!---<cfdocumentitem type="header" >
<cfoutput><p align= "right">Page #cfdocument.currentpagenumber# of #cfdocument.totalpagecount#</p></cfoutput>
</cfdocumentitem> --->
<div class="RTable">
<h3 class="RTable-h3">CHECKLIST STATS</h3>
<cfoutput>
<ul class="RTable-headingList">
<li>FROM <span class="RTable-headingList-date">#dateFormat(date1, 'mm/dd/yyyy')#</span> TO <span class="RTable-headingList-date">#dateFormat(date2, 'mm/dd/yyyy')#</span></li>
<li>LOCATIONS: <span class="RTable-headingList-locations">#locList#</span></li>
</ul>
</cfoutput>
<table class="table table-hover RTable-table">
<thead>
<tr>
<th>Associate Name</th>
<th>Location</th>
<th><small>Generated by</small>Associate</th>
<th><small>Generated by</small>Selected Location(s)</th>
<th><small>Associate Percentage of</small>Location Total</th>
</tr>
</thead>
<tbody>
<cfoutput query="GetEmployeeInfo">
<tr>
<td class="RTable-name"><cfif rnA EQ 1><strong>#assoc_name#</strong></cfif></td>
<td class="RTable-location"><cfif rnL EQ 1>#trans_location#</cfif></td>
<td>#checklistsByAssocLoc#</td>
<td>#assocChecklistsByLoc#</td>
<td>#DecimalFormat(totalChecklistsByAssocLocPct)# %</td>
<!---<td> rnA: #rnA# | rnL: #rnL# | rnTotAssoc: #rnTotAssoc# </td> --->
</tr>
<cfif rnTotAssoc EQ 1>
<tr class="RTable-row-associate-total">
<td>Associate Total</td>
<td></td>
<td>#totalChecklistsByAssoc#</td>
<td>#totalAssocChecklistsByAllFilteredLoc#</td>
<td>#DecimalFormat(totalChecklistsByLocPct)# %</td>
</tr>
</cfif>
</cfoutput>
</tbody>
</table>
</div>
<!---</cfdocument>--->
I am trying to use it like cfdocument in the <cfif FORM.Format IS "print"> do I cfexecute this table some how in replace of how I have the table? I am using this as reports and dont want to save a million reports to the server. I guess I am looking for some assistance in getting off on the right foot. Any help would be greatly appreciated.
Iv tried adding this code inside the if "print":
<cfexecute name="C:\Program Files\wkhtmltopdf\bin\wkhtmltopdf.exe"
arguments="http://path/path/checklist/wkhtmltestpage.cfm C:\temp\wkhtmlTest.pdf"
errorVariable="errorMessage"
timeout="10"
/>
But this does not download it to show the user to print.
Also for some reason its not going to the link I am specifying its making a pdf of the login page...
When WKHTMLTOPDF requests the webpage, the "user" is not authenticated and the CGI.Http_User_Agent contains "wkhtmltopdf". The remote IP of the request will also be a local IP that is configured on the server. As a result, I don't directly process any CFM scripts using WKHTMLTOPDF.
I recommend:
Use CFContent to capture generated static HTML.
Save the HTML to a public-accessible directory with a random file name.
Use the web path + random file name w/WKHTMLTOPDF command line.
After PDF is generated, delete random files.
This process will make any issues easier to troubleshoot. I prefer creating static BAT files with the same randomized filename so that I can manually re-run on the server or even locally on my on PC to further troubleshoot and view any weird messages returned by the program. There's many other command line settings you'll want to pass (margins, orientation, pagesize, header/footer, JS delay, etc), so creating a standalone BAT file with all of the command line arguments is the best approach. (I noticed in another question that you may be using CFX_Exec. I recommend using it over CFExecute.)
<!--- Simple WKHTMLTOPDF Generation Usage --->
<cfset FileID = CreateUUID()>
<cffile action="WRITE" file="#webroot#\#FileID#.htm" output="#TheHTML#">
<cfexecute name="C:\wkhtmltopdf.exe" arguments="http://mywebsite.com/#FileID#.htm C:\temp\#FileID#.pdf" timeout="30">
<cffile action="DELETE" file="#webroot#\#FileID#.htm">
To deliver the PDF file, you can either perform a 302 redirect to the randomized PDF file and let your webserver handle the mimetype (and then delete it later) or use CFContent to deliver it using a ColdFusion thread and automatically delete it:
<!--- Return PDF to browser inline or as downloadable attachment --->
<!--- <cfheader name="content-disposition" value="attachment; filename=""NiceFilename.pdf"""> --->
<cfheader name="content-disposition" value="inline; filename=""NiceFilename.pdf""">
<cfcontent type="application/pdf" file="C:\temp\#FileID#.pdf" deletefile="Yes">

Coldfusion Word Document Table - Keep With Next

I am building a report that outputs the information into a word document using Coldfusion. Some of the information is output into tables and occasionally the table is occurring at the bottom of a page and the table ends up breaking apart to two pages. How do I keep the table from breaking apart?
The format is .doc.
<cfsavecontent variable="myDocument">
<html xmlns:w="urn:schemas-microsoft-com:office:word">
<head>
<xml>
<w:WordDocument>
<w:View>Print</w:View>
<w:Zoom>100</w:Zoom>
<w:DoNotOptimizeForBrowser/>
</w:WordDocument>
</xml>
</head>
<body>
<!--- run some queries to get information --->
<!--- Display some information via normal <cfoutput> --->
<!--- Then display information in table like this --->
<cfset xtest_count = 0>
<cfloop query="test">
<cfif xtest_count eq 0>
<cfset xtest_count = xtest_count + 1>
<b><u>Speaker<cfif test.recordcount gt 1>s</cfif></u></b><br><br>
<table width="100%" cellpadding=3 cellspacing=2 border=1>
<tr>
<td><b>Title</b></td>
<td><b>Name</b></td>
<td><b>Credentials</b></td>
<td><b>Organization</b></td>
</tr>
</cfif>
<tr>
<td>#test.title#</td>
<td nowrap>#test.first_name# #test.last_name#</td>
<td>#test.credentials#</td>
<td>#test.company_name#</td>
</tr>
</cfloop>
</table>
<!--- Display More information--->
</body>
</html>
</cfsavecontent>

cfdocument issue - cfdocument has no body

I am working with Coldfusion10 and am facing this error:
The following information is meant for the website developer for debugging purposes.
Error Occurred While Processing Request
cfdocument tag has no body.
It must have a body or a source file or URL.
I checked the website and detected that cfsettings is not defined at the top or anywhere which can cause this issue, I am using it as
<cfdocument format="pdf">
<cfdocumentsection>
<cfdocumentitem type="header"></cfdocumentitem> - Footer is used too
</cfdocumentsection>
I tried using evalAtPrint= true but still no success. Am I missing something here?
Make sure you are actually putting in at the end. I'm assuming you just missed this here.
Otherwise everything seems to align up with the Wiki Docs.
I would suggest two things.
Verify you are using ColdFusion 11 Update 3. Update 3 was a major update and may have addressed this issue.
If you are using update 3, open a bug at bugbase.adobe.com
The error message you included in your question indicates that there is no content between your <cfdocument> tags. The code that you included corroborates that. If this is not your actual code then we cannot be of much help.
You need to include the content that you would like to be converted into PDF between the <cfdocument> tags. You need something like this:
<cfquery datasource="cfdocexamples" name="empSalary">
SELECT Emp_ID, firstname, lastname, e.dept_id, salary, d.dept_name
FROM employee e, departmt d
WHERE e.dept_id = d.dept_id
ORDER BY d.dept_name
</cfquery>
<cfdocument format="PDF">
<cfoutput query="empSalary" group="dept_id">
<cfdocumentsection>
<cfdocumentitem type="header">
<font size="-3"><i>Salary Report</i></font>
</cfdocumentitem>
<cfdocumentitem type="footer">
<font size="-3">Page #cfdocument.currentpagenumber#</font>
</cfdocumentitem>
<h2>#dept_name#</h2>
<table width="95%" border="2" cellspacing="2" cellpadding="2" >
<tr>
<th>Employee</th>
<th>Salary</th>
</tr>
<cfset deptTotal = 0 >
<!--- inner cfoutput --->
<cfoutput>
<tr>
<td>
<font size="-1">#empSalary.lastname#, #empSalary.firstname#</font>
</td>
<td align="right">
<font size="-1">#DollarFormat(empSalary.salary)#</font>
</td>
</tr>
<cfset deptTotal = deptTotal + empSalary.salary>
</cfoutput>
<tr>
<td align="right"><font size="-1">Total</font></td>
<td align="right"><font size="-1">#DollarFormat(deptTotal)#</font></td>
</tr>
<cfset deptTotal = 0>
</table>
</cfdocumentsection>
</cfoutput>
</cfdocument>
Copied from the ColdFusion documentation here

Next Page, for data editing

I did SEARCHing code, something like this.
--pageone.cfm--
<cfparam name="Form.studNo" default="" />
<form action="pagetwo.cfm" method="POST">
<label> Please insert ID:
<input name="studNo" value="<cfoutput>#Form.studNo#</cfoutput>" />
</label>
<input type="submit" value="Search" />
</form>
--pagetwo--
SELECT * FROM students
WHERE students_ID IN (#Form.studNo#)
On page two, I will display the details of student's info and user can edit a new information if the data is not right.
I'm thinking of, displaying data of one student per page and user can click Next for the next students (ID that has been inserted) on pageone.cfm
Can anyone help me with these?
#henry.
I did try something like this.
--example.cfm--
<CFPARAM NAME="StartRow" DEFAULT="1">
<CFPARAM NAME="DisplayRows" DEFAULT="1">
<CFQUERY NAME="getStudent" DATASOURCE="#dsn#"
CACHEDWITHIN="#CreateTimeSpan(0,0,15,0)#">
SELECT *
FROM students
</CFQUERY>
<CFSET ToRow = StartRow + (DisplayRows - 1)>
<CFIF ToRow GT getStudent.RecordCount>
<CFSET ToRow = getStudent.RecordCount>
</CFIF>
<HTML>
<HEAD>
<TITLE>Next/Previous Record Browsing</TITLE>
</HEAD>
<BODY>
<CFOUTPUT>
<H4>Displaying records #StartRow# - #ToRow# from the
#getStudent.RecordCount# data inserted.</H4>
</CFOUTPUT>
<!--- create the header for the table --->
<TABLE CELLPADDING="3" CELLSPACING="0">
<TR BGCOLOR="#888888">
<TH>Name</TH>
<TH>ID</TH>
<TH>Gender</TH>
<TH>E-mail</TH>
</TR>
<CFOUTPUT QUERY="getStudent" STARTROW="#StartRow#"
MAXROWS="#DisplayRows#">
<TR BGCOLOR="##C0C0C0">
<TD>#Name#</TD>
<TD>#ID#</TD>
<TD>#Gender#</TD>
<TD>#Email#</TD>
</TR>
</CFOUTPUT>
</TABLE>
<CFSET Next = StartRow + DisplayRows>
<CFSET Previous = StartRow - DisplayRows>
<!--- Create a previous records link if the records being displayed aren't the
first set --->
<CFOUTPUT>
<CFIF Previous GTE 1>
<A HREF="example.cfm?StartRow=#Previous#"><B>Previous #DisplayRows#
Records</B></A>
<CFELSE>
Previous Records
</CFIF>
<CFIF Next LTE getStudent.RecordCount>
<A HREF="example.cfm?StartRow=#Next#"><B>Next
<CFIF (getStudent.RecordCount - Next) LT DisplayRows>
#Evaluate((getStudent.RecordCount - Next)+1)#
<CFELSE>
#DisplayRows#
</CFIF> Records</B></A>
<CFELSE>
Next Records
</CFIF>
</CFOUTPUT>
</BODY>
</HTML>
You may use session to store the specified IDs in pageone.cfm, then go through the array of IDs and use array index as the current page number, and array length as the total page count.
However in this day and age, using JS to display a record at a time makes more sense.
Your approach is ok, but I'd recommend using existing libaries that do the trick of paginating and even constructing HTML for displaying page number buttons, next-previous links and what not.
Please revise http://paginationcfc.riaforge.org/ - a ColdFusion project that deals with pagination quite well and is very easy to implement, saving you tons of work. You could even set it do display one record per page, and the rest (next-previous buttons and maybe "page numbers") would be automagically done by the pagination component.
Ah, doing so (one big query and subsequent sub-queries) I strongly suggest caching.
HTH.

Make use of custom tag to avoid CFM files mingled with lots of HTML markups?

Have you had any success with uses of custom tag for view that increase maintainability?
Does use of custom tag have any benefit of using basic <cfinclude>?
Any custom tag you have written before that increase HTML maintainability?
Thanks
Yes and Yes.
Custom tags are awesome for separating out reusable HTML elements. The advantages are that they are scope-safe, and you can pass arguments into them to change their behavior, which you can't do with cfinclude. You can even create nested tags that change their behavior based on their parents. Most of my HTML layouts in ColdFusion look something like this:
<cf_layout>
<cf_head title="My Page>
<!--- Some local head stuff ---->
</cf_head>
<cf_content>
Lots of stuff goes here.
</cf_content>
</cf_layout>
This is obviously greatly simplified, but I can change the entire layout of a site by changing the custom tag, or even changing the custom tag folder (think dynamic this.customTagPaths for swapping out layouts.
Custom Tags don't get enough love. Embrace them, and enjoy :).
Here's another example of something I do:
<cfset arOrders = OrderService.getOrders() />
<table>
<cfloop array="#arOrders#" index="currentOrder">
<cf_orderrow order="#currentOrder#" />
</cfloop>
</table>
My OrderRow custom tag handles all of the display for displaying a row for an order in a table. This includes add/edit/delete buttons, calculations that have to happen, conditions that must be me to change the display. I can display an order row anywhere I want in an admin area. On the customer details page, on a page showing all orders in a certain time period. It makes no difference. My users know that any place they see a row describing an order, it will look just as they expect it to.
Here are a few more concrete examples of small custom tags I use across multiple applications.
cf_jquery
This includes jQuery and jQueryUI in a page, but only if no other template in the application has already added jQuery on this request. This allows me to build multiple jQuery parts to a page, and no matter what order I call them in, I'll always know that jQuery has been added to the head of the document only once.
<cfif thisTag.ExecutionMode EQ "end" AND (NOT StructKeyExists(Request, "JQueryInited") OR Request.JQueryInited EQ False)>
<cfparam name="Attributes.IncludeUI" default="False" />
<cfparam name="Request.jQueryUIInited" default="False" />
<script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1.5.1/jquery.min.js"></script>
<cfif Attributes.IncludeUI AND Request.jQueryUIInited IS False>
<link href="/shared/css/smoothness/jquery-ui-1.8.11.custom.css" rel="stylesheet" />
<script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jqueryui/1.8.11/jquery-ui.min.js"></script>
<cfset Request.jQueryUIInited = True />
</cfif>
<cfset Request.JQueryInited = True />
</cfif>
I even build tags for small information that I know will be used throughout the application in multiple places, like this cf_contact tag:
<cfif thisTag.ExecutionMode EQ "end">
<cfparam name="Attributes.Phone" default="xxx.xxx.xxxx" />
<cfparam name="Attributes.Email" default="me#you.com" />
<cfparam name="Attributes.EmailName" default="Email Support" />
<h2 style="font: bold 12px Verdana, Geneva, Arial, Helvetica, sans-serif; margin-bottom: 0;">Contact Us</h2>
<p style="text-align: left; margin-top: 0;"><cfoutput>#Attributes.Phone#</cfoutput><br />
<cfoutput>#Attributes.EmailName#</cfoutput></p>
</cfif>
Why make a tag for something so small? Why not? If I style all my contact information the same throughout an application (and I should, so users can easily identify things), then I can make a tag for it just as easily as I can make an include. If I decide I want some other programmatic behavior to happen in there, my app is already set up to use it.
cf_timeblock
This custom tag shows a set of select lists for choosing a time block (hh:mm tt). I use this all over an application dealing with scheduling, so I have a tag that will allow me to display a block for a time, and I can send in which values should be pre-selected (I'm missing validation for the attributes, but just pretend it's there for now):
<cfif thisTag.ExecutionMode EQ "end">
<cfsilent>
<!--- Get Time Blocks --->
<cfset TimeBlocks = Application.DAO.getTimeQueries() />
</cfsilent>
<select name="hour" style="width: auto; float: none;">
<cfoutput query="TimeBlocks.Hours">
<option value="#TimeBlocks.Hours.Value#"<cfif TimeBlocks.Hours.Value EQ Attributes.Hour> selected="selected"</cfif>>
#TimeBlocks.Hours.Value#
</option>
</cfoutput>
</select>:<select name="min" style="width: auto; float: none;">
<cfoutput query="TimeBlocks.Mins">
<option value="#TimeBlocks.Mins.Value#"<cfif TimeBlocks.Mins.Value EQ Attributes.Min> selected="selected"</cfif>>
#TimeBlocks.Mins.Value#
</option>
</cfoutput>
</select>
<select name="mer" style="width: auto; float: none;">
<cfoutput query="TimeBlocks.Mer">
<option value="#TimeBlocks.Mer.Value#"<cfif TimeBlocks.Mer.Value EQ FORM.mer> selected="selected"</cfif>>
#TimeBlocks.Mer.Value#
</option>
</cfoutput>
</select>
</cfif>
I use tons more than this, but they're all very application/object specific. Just about any time I start to pull something out into an include, or if I find myself copying a piece of display logic from one page to another, I start thinking about how I can push that code into a custom tag.
Here are a few more examples of custom tag libraries that I find useful.
cfUniform
Ben Nadel's POI Custom Tags
One example missing from Dan's otherwise excellent efforts is the very powerful but under-utilised capabilities custom tags have to iterate over enclosed content:
<!--- loop.cfm --->
<cfif (THISTAG.ExecutionMode EQ "Start")>
<cfparam name="attributes.index" type="variablename">
<cfparam name="attributes.iterations" type="integer">
<cfset variables.index = 1>
<cfset caller[attributes.index] = variables.index>
<cfelse>
<cfset variables.index = variables.index + 1>
<cfif variables.index lte attributes.iterations>
<cfset caller[attributes.index] = variables.index>
<cfexit method="loop">
<cfelse>
<cfexit method="exittag">
</cfif>
</cfif>
<!--- testLoop.cfm --->
<cf_loop iterations="10" index="i">
<cfoutput>[#i#]</cfoutput>Hello World<br />
</cf_loop>
The one caveat I would offer for using custom tags is that I have heard they are prone to leaking memory (I cannot substaniate this), so if using them on a heavy-traffic site, I'd load test them thoroughly before going live with them.