Recently, I needed a way to post a few forms, each with varying fields and field names, to a third-party site and wanted a way to do this efficiently. While there are several ways to approach this, I thought I would share how I, or ColdFusion really, achieved this.
So to begin, I'm going to outline some of the simple steps I came up with. Then, I'll share the ColdFusion code I used to accomplish each step. Don't worry, there's really nothing too fancy or complicated going on here. Why not? Well, because we're going to use ColdFusion to do this for us!
- Create a form with the desired (or required) fields.
- Invoke a .CFC to process the form.*
- The .CFC should do the following:
- Loop over and capture only those form fields needed to post to the third-party (or unique URL)
- Post the form fields to the third-party (or unique URL)
- Return a response to the caller:
- If successful, respond true
- If unsuccessful, respond false (or if needed, with an array of errors)
*When building this, I wanted to create something I could potentially reuse in other projects, hence the reason I'm creating a .CFC (or ColdFusion Component). Obviously, you don't necessarily have to do it this way.
As you can see, once you layout the requirements, it's much easier to begin the coding process (well, it is for me anyway).
Now, I'm going to begin by building the .CFC to handle form processing. When creating a .CFC, I think through a number of things I'm going to need in order to "return" a result back to the caller. Firstly, what am I going to return? In this case, I'm going to return either "True" or "False" so the caller can make a simple decision based on the response to either proceed or halt the process. Sure, I could return more detailed information if needed, such as CFTry/CFCatch error details and messages, etc. However, I'll attempt to stay with a more simplistic approach.
Here are a few pieces of information I'll need to gather for the .CFC:
- The contents of the form (required)
- The URL to "post" the form fields to (required)
- A listing of form fields to ignore or not post to the URL (optional, i.e., button names, etc.)
- The form method (optional, defaults to "post")
Here's the .CFC I came up with:
<!------------------------------------------------------------------------------------------------------------
Document: formProcess.cfc
Author: steve [at] stephenwithington [dot] com (Steve Withington)
Creation Date: 02/19/2009
Copyright: (c) 2009 Stephen J. Withington, Jr. | www.stephenwithington.com
Purpose: I process simple forms (not files at this time) and post the data to third-party URLs.
I'm setup to return boolean (true on success, false on failure).
However, can easily be modified to return an array of error messages if needed:
a) change the returntype from "boolean" to "any"
b) uncomment the return code at the end of the .cfc and comment out the existing cfreturn
METHODS: 1) processForm()
Revision Log:
MM/DD/YYYY - sjw - comments.
------------------------------------------------------------------------------------------------------------->
<cfcomponent
displayname="Process Forms"
hint="Pass me some forms and a url to post the data to, and all your wildest dreams will come true."
output="no">
<!--- 1) processForm() --->
<cffunction
name="processForm"
displayname="Process Forms"
access="public"
returntype="boolean"
output="no">
<cfargument
name="formData"
type="struct"
required="true"
default="" />
<cfargument
name="formURL"
type="string"
required="true"
default="" />
<cfargument
name="fieldsIgnore"
type="string"
required="false"
default="SUBMIT,ISSUBMITTED" />
<cfargument
name="formMethod"
type="string"
required="false"
default="post" />
<cfset var isProcessed = true />
<cfset var local = {} />
<cfset local.errors=[] />
<cfset local.methodList = "get,post,put,delete,head,trace,options" />
<cfset local.formfields = arguments.formData.fieldnames />
<!--- validate formMethod --->
<cfif not listFindNoCase(local.methodList,arguments.formMethod)>
<cfset arrayAppend(local.errors, "arguments.formMethod must be one of the following: " & local.methodList) />
<cfset isProcessed = false />
</cfif>
<!--- validate formURL --->
<cfif not isValid("url", arguments.formURL)>
<cfset arrayAppend(local.errors, "arguments.formURL must be a valid URL") />
<cfset isProcessed = false />
</cfif>
<cfif not arrayLen(local.errors)>
<cftry>
<!--- post the form fields --->
<cfhttp method="#arguments.formMethod#" url="#arguments.formURL#" charset="utf-8">
<!--- loop over the form fields to create the CFHttpParams --->
<cfloop list="#local.formFields#" index="field">
<!--- only post form fields that are NOT in the fieldsIgnore list --->
<cfif not listFindNoCase(arguments.fieldsIgnore,field)>
<!--- as each form field is processed, add it to the list of fields to ignore --->
<cfset arguments.fieldsIgnore = listAppend(arguments.fieldsIgnore,field)>
<cfhttpparam name="#field#" type="FormField" value="#arguments.formData[field]#" />
</cfif>
</cfloop>
</cfhttp>
<cfcatch>
<cfset arrayAppend(local.errors, "CFHttp cfCatch.Message: " & cfcatch.message) />
<cfset arrayAppend(local.errors, "CFHttp cfCatch.Detail: " & cfcatch.detail) />
<cfset isProcessed = false />
</cfcatch>
</cftry>
<cfif not arrayLen(local.errors)>
<cfif cfhttp.statusCode neq "200 OK">
<cfset arrayAppend(local.errors, "CFHttp.errorDetail: " & cfhttp.errorDetail) />
<cfset isProcessed = false />
</cfif>
</cfif>
</cfif>
<cfreturn isProcessed />
<!---
use this only if desiring more detailed response.
also need to change returntype from "boolean" to "any"
and remove or comment out the previous <cfreturn isProcessed /> above
--->
<!---<cfif arrayLen(local.errors)>
<cfreturn local.errors />
<cfelse>
<cfreturn isProcessed />
</cfif>--->
</cffunction>
</cfcomponent>
So, now we need a simple form to illustrate how I used the .CFC.
<!--- formSample.cfm --->
<cfsilent>
<cfparam name="form.isSubmitted" default="false" />
<cfparam name="formResult" default="Not submitted yet." />
<cfif form.isSubmitted>
<!--- call the .cfc (i'm now the 'caller') --->
<cfinvoke
component="formProcess"
method="processForm"
returnvariable="isProcessed">
<!--- obviously, here is where you need to make some modifications --->
<cfinvokeargument name="formURL" value="http://#cgi.HTTP_HOST#/formHandler.cfm" />
<cfinvokeargument name="fieldsIgnore" value="SUBMIT,ISSUBMITTED" />
<cfinvokeargument name="formMethod" value="post" />
<cfinvokeargument name="formData" value="#form#" />
</cfinvoke>
<cfif isDefined("isProcessed")>
<cfset formResult = isProcessed />
<cfelse>
<cfset formResult = "Error!" />
</cfif>
</cfif>
</cfsilent>
<style type="text/css">
body, select, input, textarea {
font-family: Arial, Helvetica, sans-serif;
font-size: 11px;
}
.space {
padding: 10px;
margin: 10px;
border: 1px solid black;
width: 200px;
background-color: #F5F5F5;
}
.field {
padding: 10px;
}
</style>
<div class="space">
<cfdump var="#form#" label="form" />
</div>
<div class="space">
<cfdump var="#formResult#" label="formResult" />
</div>
<div class="space">
<cfform
name="myForm"
id="myForm"
action="#GetFileFromPath(GetBaseTemplatePath())#"
method="post">
<div class="field">
<strong><label for="nameFirst">First Name:</label></strong><br />
<cfinput type="text" name="nameFirst" id="nameFirst" size="25" />
</div>
<div class="field">
<strong><label for="nameLast">Last Name:</label></strong><br />
<cfinput type="text" name="nameLast" id="nameLast" size="25" />
</div>
<div class="field">
<strong><label for="state">State</label></strong><br />
<cfselect id="state" name="state" >
<option value="">- Select -</option>
<option value="OH">Ohio</option>
<option value="IL">Illinois</option>
<option value="FL">Florida</option>
</cfselect>
</div>
<div class="field">
<strong><label for="comments">Comments:</label></strong><br />
<cftextarea name="comments" id="comments" rows="5" cols="25"></cftextarea>
</div>
<div class="field">
<cfinput type="submit" name="submit" label="Submit" value="Submit" validate="submitonce" onclick="javascript:alert('Your information will now be submitted.\nPlease be patient as we process your information.')" />
</div>
<cfinput type="hidden" name="isSubmitted" id="isSubmitted" value="true" />
<cfinput type="hidden" name="thirdPartyFormName" value="SomeUniqueNameKnownByThirdParty" />
<cfinput type="hidden" name="thirdPartyClientID" value="007" />
</cfform>
<script type="text/javascript" language="JavaScript">
document.forms['myForm'].elements['nameFirst'].focus();
</script>
</div>
For good measure, I've also created a simple handler so you can test everything locally:
<!--- formHandler.cfm --->
<cfset request.RootDirectory = expandpath('/') />
<cfset doNotProcess = "" />
<cftry>
<cffile
action="append"
file="#request.rootdirectory#working/form.txt"
output="************ ID: #createUUID()# Submitted: #dateformat(now(), 'mm/dd/yyyy')# #timeformat(now(), 'h:mm:ss tt')# ************"
addnewline="yes"
charset="utf-8" />
<cfloop list="#form.fieldnames#" index="field">
<cfif not ListFindNoCase(doNotProcess,field)>
<cfset doNotProcess = ListAppend(doNotProcess,field)>
<cffile
action="append"
file="#request.rootdirectory#working/form.txt"
output="Fieldname: #field# Value: #form[field]#"
addnewline="yes"
charset="utf-8" />
</cfif>
</cfloop>
<cfcatch>
<cffile
action="append"
file="#request.rootdirectory#working/form.txt"
output="Error! Message: #cfcatch.Message# Detail: #cfcatch.Detail#"
addnewline="yes"
charset="utf-8" />
</cfcatch>
</cftry>
That's pretty much it. I certainly hope this inspires you to think of other ways to use ColdFusion. In the mean time, please let me know if this has helped you! Thanks!