This idea came from a discussion today on the cf-newbie mail list. Zelda (real name, or real name in the sig, either way, cool) described a situation where she need to...
- Process a form
- Turn the form data into a PDF
- Email the PDF
This isn't a complex process, but it's an interesting example of where you can combine simple tasks in ColdFusion into something a bit more complex. I thought it would make an excellent entry in my ColdFusion Sample series. What follows is my take on how one could do this. Let's begin by creating a simple form.
<p>
Thank you for your interest in ordering form Kuat Drive Yards. Please fill out the
form below and be as complete as possible with your needs.
</p> <cfoutput>
<cfif structKeyExists(variables, "errors")>
<p>
<b>Please correct these errors:<br>
#errors#
</b>
</p>
</cfif> <p>
<label for="name">Your Name:</label>
<input type="text" name="name" id="name" value="#form.name#"><br/>
</p> <p>
<label for="email">Your Email:</label>
<input type="text" name="email" id="email" value="#form.email#"><br/>
</p> <p>
<label for="orderrequest">Your Request:</label><br/>
<textarea name="orderrequest" id="orderrequest">#form.orderrequest#</textarea>
</p>
</cfoutput> <p>
<input type="submit" name="submit" value="Send Request">
</p>
</form>
<form method="post">
<h2>Order Request / Kuat Drive Yards</h2>
Nothing too complex here. I've got 3 main form fields and a bit of logic to handle errors. Where that variable comes from, and the form fields themselves, will get to in a minute. With a bit of styling, this is our result.
Ok, now let's actually build in the form processing logic. In this version, I've got everything done except what we want to happen with the form when everything is entered correctly.
<cfset showForm = true>
<cfif structKeyExists(form, "submit")>
<cfset errors = "">
<cfset form.name = trim(htmlEditFormat(form.name))>
<cfset form.email = trim(htmlEditFormat(form.email))>
<cfset form.orderrequest = trim(htmlEditFormat(form.orderrequest))> <cfif form.name is "">
<cfset errors = errors & "Include your name.<br/>">
</cfif>
<cfif form.email is "" or not isValid("email", form.email)>
<cfset errors = errors & "Include your email address.<br/>">
</cfif>
<cfif form.orderrequest is "">
<cfset errors = errors & "Include your order request.<br/>">
</cfif> <cfif errors is "">
<cfset showForm = false>
</cfif> </cfif> <html> <head>
<title>Order Request / Kuat Drive Yards</title>
<style>
#orderForm {
width: 500px;
margin-left: auto;
margin-right: auto;
margin-top: 10px;
background-color: white;
padding: 10px;
} input[type='text'] {
width: 250px;
float: right;
}
textarea {
width: 100%;
height: 200px;
} body {
background-color: #c0c0c0;;
}
</style>
</head> <body> <div id="orderForm"> <cfif showForm>
<form method="post"> <h2>Order Request / Kuat Drive Yards</h2> <p>
Thank you for your interest in ordering form Kuat Drive Yards. Please fill out the
form below and be as complete as possible with your needs.
</p> <cfoutput>
<cfif structKeyExists(variables, "errors")>
<p>
<b>Please correct these errors:<br>
#errors#
</b>
</p>
</cfif> <p>
<label for="name">Your Name:</label>
<input type="text" name="name" id="name" value="#form.name#"><br/>
</p> <p>
<label for="email">Your Email:</label>
<input type="text" name="email" id="email" value="#form.email#"><br/>
</p> <p>
<label for="orderrequest">Your Request:</label><br/>
<textarea name="orderrequest" id="orderrequest">#form.orderrequest#</textarea>
</p> </cfoutput>
<p>
<input type="submit" name="submit" value="Send Request">
</p>
</form> <cfelse>
<h2>Thank you</h2>
<p>
Your order request has been received. Thank you.
</p>
</cfif> </div> </body>
</html>
<cfparam name="form.name" default="">
<cfparam name="form.email" default="">
<cfparam name="form.orderrequest" default="">
Scrolling from the top to the bottom, you can see I've paramed my form fields first. This allows me to always assume they exist and use them in the form right away. Whenever an error occurs and we redisplay the form, this allows for keeping their previous data in the form. If you don't do this, your users will hate you. I'm using a simple variable, showForm, that will keep track of whether or not we need to display the form.
My form processing logic is rather simple. Notice I trim and htmlEditFormat all the fields. Then I simply ensure they are all not blank. Only the email field gets a bit of extra love with the isValid function. Now I should be able to test my form. I can try leaving a few fields blank, hitting submit, and ensuring I get my error messages. The error message should change based on what I did wrong and the form should remember the fields I entered. Nice. Ok, now for the last bits.
First - generating a PDF is incredibly simple. I create my PDF from the form input using the cfdocument tag.
<!--- create a PDF from the request: --->
<cfdocument format="pdf" name="pdfData">
<cfoutput>
<h2>Order Request</h2>
<p>
Order made by #form.name# (#form.email#) on #dateFormat(now(), "mm/dd/yy")# at #timeFormat(now(), "h:mm tt")#.
</p>
<p>
The request was for:
</p>
<p>
#form.orderrequest#
</p>
</cfoutput>
</cfdocument>
While you've probably seen cfdocument before, make note of the name attribute. This tells ColdFusion to store the PDF bits in a variable instead of saving it or writing it out to screen. Now let's send our email:
<cfmail to="raymondcamden@gmail.com" from="#form.email#" subject="Order Request">
<cfmailparam disposition="attachment" file="request.pdf" type="application/pdf" content="#pdfData#" >
An order request has been filed. See the attached PDF for details.
</cfmail>
Nothing too fancy here. I send the email and attach the document. Make note of the content attribute of the cfmailparam tag. This allows me to attach the PDF and skip saving it to the file system. This is not in the PDF for the CFML 9 reference but is in the online version.
And that's it. If you're curious about the PDF I've attached it to the blog entry. And yes - this is a bit of a silly example. I didn't really need to create a PDF for 3 simple fields, but if your business process requires a PDF to be generated and emailed, hopefully this demonstrates how simple it is in ColdFusion. The full code of the template I used may be found below.
<cfset showForm = true>
<cfif structKeyExists(form, "submit")>
<cfset errors = "">
<cfset form.name = trim(htmlEditFormat(form.name))>
<cfset form.email = trim(htmlEditFormat(form.email))>
<cfset form.orderrequest = trim(htmlEditFormat(form.orderrequest))> <cfif form.name is "">
<cfset errors = errors & "Include your name.<br/>">
</cfif>
<cfif form.email is "" or not isValid("email", form.email)>
<cfset errors = errors & "Include your email address.<br/>">
</cfif>
<cfif form.orderrequest is "">
<cfset errors = errors & "Include your order request.<br/>">
</cfif> <cfif errors is ""> <!--- create a PDF from the request: --->
<cfdocument format="pdf" name="pdfData">
<cfoutput>
<h2>Order Request</h2>
<p>
Order made by #form.name# (#form.email#) on #dateFormat(now(), "mm/dd/yy")# at #timeFormat(now(), "h:mm tt")#.
</p>
<p>
The request was for:
</p>
<p>
#form.orderrequest#
</p>
</cfoutput>
</cfdocument> <cfmail to="raymondcamden@gmail.com" from="#form.email#" subject="Order Request">
<cfmailparam disposition="attachment" file="request.pdf" type="application/pdf" content="#pdfData#" >
An order request has been filed. See the attached PDF for details.
</cfmail>
<cfset showForm = false>
</cfif> </cfif> <html> <head>
<title>Order Request / Kuat Drive Yards</title>
<style>
#orderForm {
width: 500px;
margin-left: auto;
margin-right: auto;
margin-top: 10px;
background-color: white;
padding: 10px;
} input[type='text'] {
width: 250px;
float: right;
}
textarea {
width: 100%;
height: 200px;
} body {
background-color: #c0c0c0;;
}
</style>
</head> <body> <div id="orderForm"> <cfif showForm>
<form method="post"> <h2>Order Request / Kuat Drive Yards</h2> <p>
Thank you for your interest in ordering form Kuat Drive Yards. Please fill out the
form below and be as complete as possible with your needs.
</p> <cfoutput>
<cfif structKeyExists(variables, "errors")>
<p>
<b>Please correct these errors:<br>
#errors#
</b>
</p>
</cfif> <p>
<label for="name">Your Name:</label>
<input type="text" name="name" id="name" value="#form.name#"><br/>
</p> <p>
<label for="email">Your Email:</label>
<input type="text" name="email" id="email" value="#form.email#"><br/>
</p> <p>
<label for="orderrequest">Your Request:</label><br/>
<textarea name="orderrequest" id="orderrequest">#form.orderrequest#</textarea>
</p> </cfoutput>
<p>
<input type="submit" name="submit" value="Send Request">
</p>
</form> <cfelse>
<h2>Thank you</h2>
<p>
Your order request has been received. Thank you.
</p>
</cfif> </div> </body>
</html>
<cfparam name="form.name" default="">
<cfparam name="form.email" default="">
<cfparam name="form.orderrequest" default="">
Archived Comments
Thanks . . its been a while since I had to do this . .great refresher!
Thats a good example of using a self posting form to generate a pdf document and mail it. One thing I also do is use the name attribute on the submit button, but I differ in that I reference it as FORM.Submit, then you can process the form submit with an if statement like <cfif FORM.Submit eq "Create PDF">, I use this often, and also you can have more than one submit button to process different methods of processing the form.
Another way to do this would be to grab the entire innerHTML of the ORIGINAL form in Javascript and use an Ajax request to handle the PDF creation / emailing. This could work nicely as a generic and reusable function that uses the on-screen formatting as-is. Saves having to re-do the form at the back end. You could perhaps just top and tail it and grab/validate the email address.
@ Ray
As usual, you have thrown my well planned day out of schedule - by tickling my never ending thirst for new knowledge about ColdFusion :)
I have never had need to create and add a PDF document as a mail attachment, but due to a conversation with another developer yesterday, in particular about PDF document creation, I was already well primed for this article.
Just a couple of things:
1/. There is no need to use the cfmailparam 'disposition' attribute, with a value of 'attachment' in your example, because it is the default value and works fine without it. Of course, it does no harm using it, but I have a pet hate of using things unnecessarily :)
2/. I was a bit confused by what initially looked like you 'Request' scoping the cfmailparam 'file' attribute value. I eventually realised that 'request.pdf' was just the file name, it would have been clearer if named order_request.pdf. I also discovered that it could be just named order_request without the .pdf, as that is covered by the 'type' attribute value, and that if I put whitespace in the file name i.e. 'order request', ColdFusion would name it with an incrementing (each send), alpha prefixed file number i.e. ATT12345.pdf
+Chris:
So using filename="x" would create an attachment called x.pdf? You sure? Even if it worked, I just feel better supplying a complete filename. :)
+Michael G Workman: I did use the name of the form button. What you mean - if I may paraphrase you - is that you use the _value_ of the button to allow for multiple types of actions for the form. That certainly makes sense, but for a form with one action, it's simpler to just check for the existence. :)
@ Ray
Re: So using filename="x" would create an attachment called x.pdf? You sure?
Absolutely. Double checked this. Mind you, it seems this is just a case of ColdFusion being lenient because there is binary data for content.
The Adobe documentation is basically correct, but easily misinterpreted...
Attaches a file in a message. Mutually exclusive with name attribute. The file is MIME encoded before sending.
To be clear, I'm aware it is mutually exclusive with name, and the point of it in general, which is why I used it, but I was not aware that leaving the extension off the file name would force CF to add it. I still say you should just go ahead and supply it to be safe .
Ray, I have a required real estate form that looks like it was created by un-trained monkeys. I am definitely going to create a form to populate the PDF. My plan is to populate the PDF and post the information to a database for use later and in other forms. Can that be done with one web form?
As far as I know, yes. I've not done form population in a PDF with CF, but it can be done with cfpdfform. You can take the same data and save it to the db too.
What happens with the pdf if the email doesn't get sent right away? We use external mail servers (exchange or another hosted option) and if an email can't be sent and gets saved in the undelivr folder we move it back to the spool folder to try again. Will the email refer to a temporary object properly?
On my sites attachments are always saved to the server so that when it gets re-sent the file location is clear. If need be the email then can also have a link to the attachment on the server if need be.
That's what I'm seeing here on my laptop. The mail was left in the Undeliver folder and the tmp file still exists. I assume not forever though.
I've spent hours trying to get this to work, but every time it tries to generate the PDF, the page will timeout. Even a very basic cfdocument will throw this error: 503 Request timed out waiting to execute.
Could this have something to do with me running the Developer Edition of CF?
@Indy
The developer edition works fine with this. Other than that, I don't know what is causing your problem
Try turning off the cfdocument and see if it helps. You will get an error trying to email it obviously, but see if it gets there.
A simple cfmail will work perfectly, it's only the cfdocument that fails.
Something as simple as this will cause a timeout
<cfdocument format="pdf" name="tester">
<cfoutput>
Hello #name#
</cfoutput>
</cfdocument>
@Indy
Have you tried running cfdocument on its own?
<cfset name = "Indy">
<cfdocument format="pdf" name="tester">
<cfoutput>
Hello #name#
</cfoutput>
</cfdocument>
<cfdump var="#tester#">