I have a cfm page with html and css, needed for an email template. I have a sender.cfm file, which will include the template file and dynamically replace variables in the template file.
Below is a part of the template file
<td valign="top" align="left" style="padding:0 0 10px 0;">
<p style="font-family:Arial, Helvetica, sans-serif; font-size:14px; line-height:18px; margin:8px 10px 10px 0; padding:0; color:#102044;">
[INSERT QUESTION 1]
</p>
</td>
Now in the sender file, I have a cfquery which is fetching the questions. I am doing something like this:
<cfsavecontent variable="questions">
<cfoutput><strong>#get_ques_text.questiontext#</strong></cfoutput>
</cfsavecontent>
<cfinclude template="weekly_template.cfm">
<cfset nl_template = replaceNoCase(nl_template, "[INSERT EMAIL TITLE]"
, "WEEKLY EMAIL") />
<cfset nl_template = replaceNoCase(nl_template, "[INSERT QUESTION 1]"
, questions)/>
I want to be able to get the list of all the questions in the cfsavecontent variable. However, in the code above, I am only getting the first one. Any ideas why?
If you reference a query value just as queryName.columnName, then you are referencing one of:
the first row's value for that column;
the entire column, as an array;
depending on the context. In the context you're doing so - where CF is expecting a string - you'll just be getting the first row's value for that column.
If you want to iterate over the whole query, you need to tell ColdFusion that, via:
<cfloop query="yourQuery">
In your case, something like this:
<cfsavecontent variable="questions">
<cfloop query="get_ques_text">
<cfoutput><strong>#questiontext#</strong></cfoutput>
</cfloop>
</cfsavecontent>
Related
I have a query that gets data for a sales report for a group of stores.
I need to create a PDF of the monthly sales for each store and then email the sales manager the report for his/her store only.
The create PDF part is working fine. It creates a separate report for each store.
When I try to email the report, it sends one report for each record instead of one report for all sales people. EX: If there are 4 people on the report for store #1, the sales manager will get 4 reports instead on 1 report.
Here is my code: Creating the PDF
<cfoutput query="data" group="site_name">
<cfdocument format="pdf" overwrite="yes" fontembed="yes"
pagetype="Letter"
filename="xxxxxxxxxxxxxxxsite_name#.pdf" margintop="0.25"
marginbottom="0.25" marginleft="0.25" marginright="0.25"
backgroundvisible="yes">
<table width="100%" align="center" border="1" cellpadding="5"
cellspacing="0">
<tr bgcolor="##e1e8f7">
<td colspan="8" align="center">Sales Report -#today#-#site_name#</td>
</tr>
<tr align="center">
<td>Sales Person</td>
<td>Total Sales</td>
</tr>
<cfoutput>
<tr>
<td>#firstName# #lastName#</td>
<td>#totalSales#</td>
</tr>
</cfoutput>
</table>
</cfdocument>
</cfoutput>
CFMAIL
<cfmail query="data" to="#salesmanageremail#" subject="monthly sales
report"
type="html">
<style type="text/css">
body { font-size: 12px; font-family: Arial; }
</style>
<body>
Report attached.
</body>
<cfsilent>
<cfmailparam file="xxxxxxxxxxxxxxxsite_name#.pdf">
</cfsilent>
</cfmail>
How do I email one report per store containing all of the data to each sales manager?
You can treat your cfmail tag as a cfoutput tag. That includes using the group attribute as you did when creating the pdf.
Alternatively, you can put the cfmail tag within the cfoutput block you are using to create the pdf. All things considered, that's the approach I would take. That way you send the correct file for each site.
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
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.
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.
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.