Tuesday, January 29, 2008

Submit PDF Form as HTML with ColdFusion - Part 2

In Part 1 I showed how I used iText to add a simple button to an existing form, that would submit the information as an HTML form. In Part 2 I will show the ColdFusion code I used to process the PDF form information and save it to a database table. I deliberately kept it simple for clarity. So you could certainly improve upon it.

Anyway, since I wanted to store the information in my database, the first thing I did was create a simple table. Note, the syntax below is MS SQL Server specific.


CREATE TABLE LogPDFForm (
PdfFormID int identity(1,1),
PersonName varchar(100) NULL,
Address varchar(100) NULL,
PostalCode varchar(20) NULL,
Email varchar(100) NULL,
SubmitDate datetime NOT NULL
)

Next, I created the cfm page that will do the processing. Due to the settings used, the pdf form information will be submitted using http post. However, the form field values will be submitted as a delimited string like this

address=98+booch+street&email=test%40test.com&name=test+%3d+abc&postal_code=1234&sendButton=


So I first grabbed the form content using the GetHttpRequestData() function. Then used a simple loop to extract and decode the form field values. I chose to store them values in the FORM scope, but you could use a different scope if you prefer.

<!--- extract the fields from the request content ---->
<cfset queryString = GetHttpRequestData().content>
<cfloop list="#queryString#" index="pair" delimiters="&">
<cfif listLen(pair, "=")>
<cfset key = URLDecode(listFirst(pair, "="))>
<cfset value = URLDecode(listRest(pair, "="))>
<cfset form[ trim(key) ] = trim(value)>
</cfif>
</cfloop>

Next, I performed a bit of validation and then inserted the extracted values into my database table. As you can see the code sets a status variable to indicate whether the insert succeeded or failed. You will see why in a moment.

<cftry>
<cfquery name="logForm" datasource="MyDataSource">
INSERT INTO LogPDFForm ( PersonName, Address, PostalCode, Email, SubmitDate )
VALUES
(
<cfqueryparam value="#trim(form.Name)#" cfsqltype="cf_sql_varchar">,
<cfqueryparam value="#trim(form.Address)#" cfsqltype="cf_sql_varchar">,
<cfqueryparam value="#trim(form.Postal_Code)#" cfsqltype="cf_sql_varchar">,
<cfqueryparam value="#trim(form.Email)#" cfsqltype="cf_sql_varchar">,
<cfqueryparam value="#now()#" cfsqltype="cf_sql_timestamp">
)
</cfquery>
<cfset responseMessage = "Your form was successfully submitted!">

<cfcatch>
<cfset responseMessage = "Unable to process your form at this time. Please try again later.">
</cfcatch>
</cftry>

Finally, the code returns the status message back to Adobe Reader using FDF. The Reader will process this like a flash or javascript alert box. So the end user will receive feedback about whether or not their form information was processed.

<!--- send a response --->
<cfsetting enablecfoutputonly="true" showdebugoutput="false">
<cfcontent type="application/vnd.fdf">
<cfoutput>%FDF-1.2
%âãÏÓ
1 0 obj
<<
/FDF << /Status (#responseMessage#)>>
>>
endobj
trailer
<<
/Root 1 0 R

>>
%%EOF
</cfoutput>

Then I was ready to test the form. So I opened up the new PDF created in Part 1, entered some information, clicked Send Form and voila! It worked.



That is it for now. As always, comments/corrections/suggestion are welcome!

ReceivePDFForm.cfm

<!--- disable output so it does not interfere with fdf response --->
<cfsetting enablecfoutputonly="true" showdebugoutput="false">

<cfset wasValidated = true>
<cfset responseMessage = "">

<!--- extract the fields from the request content ---->
<cfset queryString = GetHttpRequestData().content>
<cfloop list="#queryString#" index="pair" delimiters="&">
<cfif listLen(pair, "=")>
<cfset key = URLDecode(listFirst(pair, "="))>
<cfset value = URLDecode(listRest(pair, "="))>
<cfset form[ trim(key) ] = trim(value)>
</cfif>
</cfloop>

<!--- verify the required fields exist and are non empty --->
<cfset requiredFields = "Name,Address,Postal_Code,Email">
<cfloop list="#requiredFields#" index="fieldName">
<cfif NOT structKeyExists(form, fieldName) OR NOT len( form[fieldName] ) >
<cfset wasValidated = false>
<cfbreak>
</cfif>
</cfloop>

<cfif wasValidated>
<cftry>
<cfquery name="logForm" datasource="MyDataSource">
INSERT INTO LogPDFForm ( PersonName, Address, PostalCode, Email, SubmitDate )
VALUES
(
<cfqueryparam value="#trim(form.Name)#" cfsqltype="cf_sql_varchar">,
<cfqueryparam value="#trim(form.Address)#" cfsqltype="cf_sql_varchar">,
<cfqueryparam value="#trim(form.Postal_Code)#" cfsqltype="cf_sql_varchar">,
<cfqueryparam value="#trim(form.Email)#" cfsqltype="cf_sql_varchar">,
<cfqueryparam value="#now()#" cfsqltype="cf_sql_timestamp">
)
</cfquery>
<cfset responseMessage = "Your form was successfully submitted!">

<cfcatch>
<cfset responseMessage = "Unable to process your form at this time. Please try again later.">
</cfcatch>
</cftry>
<cfelse>
<cfset responseMessage = "Unable to process your form due to missing information. All fields are required.">
</cfif>

<!--- send a response --->
<cfcontent type="application/vnd.fdf">
<cfoutput>%FDF-1.2
%âãÏÓ
1 0 obj
<<
/FDF << /Status (#responseMessage#)>>
>>
endobj
trailer
<<
/Root 1 0 R

>>
%%EOF
</cfoutput>

6 comments:

Jason July 2, 2008 at 10:40 PM  

I tried this form submission but I got this error:-



Unable to locate the form, because the FDF lacks an /F key. If the FDF came back as a result of a submit from a PDF form, then the URL is probably missing "#FDF" at the end.

cfSearching July 3, 2008 at 4:34 AM  

@Jason,

I copied and pasted the code from Part I and Part II above. To ensure the blogger did not mangle the code. It works perfectly for me.

Silly question, but did you copy+paste the code exactly? What versions of the following are you using?

- CF
- iText
- Adobe Reader

Jason July 3, 2008 at 5:22 AM  

Sorry, I've "cfcontent" the pdf file in the browser and got that error. It works perfectly fine when I open the file directly.

Anyway, thanks for the quick response.

cfSearching July 3, 2008 at 7:35 AM  

@Jason,

Yes, I receive same error with Internet Explorer when PDF is displayed in the browser, rather than downloaded.

I am serving up pdf's for download (ie using cfheader + attachment, not inline). So I am not certain why that is happening. I will have to look into this one.

Jose March 25, 2009 at 8:19 PM  

Very nice article!

However, is there a way to place the "Send Form" button in a different location? I've tried this:

newButton.setAlignment( PushbuttonField.ALIGN_CENTER );

but it's throwing an error.

Thanks!
Jose

cfSearching March 26, 2009 at 1:37 PM  

@Jose,

The button location is determined by the dimensions (ie Rectangle settings).

// step 4: set the button dimensions
buttonDimen = Rectangle.init( ... );

com.lowagie.text.Rectangle

  © Blogger templates The Professional Template by Ourblogtemplates.com 2008

Header image adapted from atomicjeep