I'm working on a project that requires state to be passed
via form fields (i.e. a multi-step wizard.) This means
that I end up needing to pass hidden form fields from
page to page. This normally requires a funky series of
if/elses to determine which values to include on each
form. Also, as things change from one form to another,
you'd need to update each page in the process to include
the hidden form field required, otherwise the value gets
lost.
Anyway, I started thinking it would be nice if I didn't
have to write any if/else statements and just have the
necessary fields required automatically passed. Well,
if you're thinking "Why not just loop over the form scope?"
Well, that's only part of the solution. You really need
to exclude fields that are already on the page.
Fortunately, several years ago I had written some code that
was part of a much bigger project, that could parse a string
and find form fields and extra information about about the
fields. Harvesting the code I had already written, I was
able to whip out a tag that would parse the content between
the start/end tags, look for any form fields in the content
and exclude those fields from being copied as hidden form fields.
Now I can very easily pass form state information from page
to page without having any dependencies from one page to the
next.
So, what's the code look like? Glad you asked! Here it is:
<cfsilent>
<!-----------------------------------------------------------------------------
name: copyFormFields
author: Dan G. Switzer, II
description:
This tag will copy anything from the form scope into hidden form fields.
The field "fieldnames" will automatically be removed (as this is a special
CF form field.) You can specify other fields be excluded by using the
"exclude" attribute
------------------------------------------------------------------------------>
<!---//
a list of fields to exclude
//--->
<cfparam name="attributes.exclude" type="string" default="" />
<!---//
specify the exact case of field names. separate each entry with
a comma. (useful for creating fields that must be accessed via
JS, where the case is sensitive.)
//--->
<cfparam name="attributes.useNames" type="string" default="" />
<cfswitch expression="#thisTag.executionMode#">
<!---//
only run the code in the start mode once
//--->
<cfcase value="start">
<!---//
remove whitespacing from the lists
//--->
<cfset attributes.exclude = reReplace(attributes.exclude, "[[:space:]]", "", "all") />
<cfset attributes.useNames = reReplace(attributes.useNames, "[[:space:]]", "", "all") />
</cfcase>
</cfswitch>
<!---// kill processing of this tag an end tag is specified and the mode is start //--->
<cfif (thisTag.executionMode eq "start") and (thisTag.hasEndTag)>
<cfexit method="exitTemplate" />
</cfif>
<!---// create a struct to hold the fields we'll be copying //--->
<cfset stFields = structNew() />
<!---// exclude the "fieldnames" field automatically //--->
<cfset lstExcludeFields = listAppend(attributes.exclude, "fieldnames") />
<!---//
if there is any content, look for form fields and if any are found, remove them from
list of fields to be included. we do not want duplicate fields to be include when
copy data
//--->
<cfif len(trim(thisTag.generatedContent)) gt 0>
<!---// find anything that looks like a form field //--->
<cfset sRegEx = "((<input[^>]*[[:space:]]type=""?(text|radio|hidden|checkbox|password)""?[^>]*>)|(<textarea[^>]*>)|(<select[^>]*>))" />
<!---// get the information about the field //--->
<cfset stFind = reFindNoCase(sRegEx, thisTag.generatedContent, 1, "true") />
<!---// loop through the content and find all the tags //--->
<cfloop condition="stFind.pos[1] GT 0">
<!---// get the complete tag //--->
<cfset sTag = mid(thisTag.generatedContent, stFind.pos[1], stFind.len[1]) />
<!---// find the "name" attribute of the tag //--->
<cfset sName = reReplaceNoCase(sTag, "([^>]*[[:space:]]name=""?)([^""[:space:]]+)([""[:space:]>][^>]*>)", "\2", "all") />
<!---// add these field name to the list of fields to exclude //--->
<cfset lstExcludeFields = listAppend(lstExcludeFields, sName) />
<!---// find the next tag after the last one //--->
<cfset stFind = reFindNoCase(sRegEx, thisTag.generatedContent, stFind.pos[1] + stFind.len[1], "true") />
</cfloop>
</cfif>
<!---//
loop through the current form scope and build the list of hidden form
fields we'll need to copy
//--->
<cfloop item="sField" collection="#form#">
<!---//
if the field is not supposed to be included in the output, skip it.
this includes fields in the "exclude" attribute, or fields found
between the start/end tag
//--->
<cfif listFindNoCase(lstExcludeFields, sField) eq 0>
<!---//
look to see if we're supposed to use a specific case for this field.
this is basically done to support JavaScript, which is case sensitive.
since all keys in the "form" scope are in uppercase, this allows us
to specify the exact case a form field name should be in
//--->
<cfset iSpecificName = listFindNoCase(attributes.useNames, sField) />
<!---// a specific casing was specified, so use that exact case now //--->
<cfif iSpecificName gt 0>
<cfset sField = trim(listGetAt(attributes.useNames, iSpecificName)) />
</cfif>
<!---// store the field to be copied //--->
<cfset stFields[sField] = form[sField] />
</cfif>
</cfloop>
</cfsilent>
<cfoutput><cfloop item="sField" collection="#stFields#"><input type="hidden" name="#sField#" value="#htmlEditFormat(stFields[sField])#" />
</cfloop></cfoutput>
So let's say you had a page where you'd be getting 3 form fields. We'll call
them field1, field2, and field3. If you want to make sure they get
included as hidden form fields, you'd use the following code:
<form>
<cf_copyFormFields>
<input type="hidden" name="isHidden" value="true" />
<p>
Name:
<input type="text" name="fName" value="" />
</p>
<p>
Mailing List:
<input type="radio" name="bMailingList" value="true" /> yes
<input type="radio" name="bMailingList" value="false" /> no
</p>
<p>
Work/Home:
<select name="place">
<option value="home">Home</option>
<option value="work">Work</option>
</select>
</p>
</cf_copyFormFields>
</form>
The above code would produce something that looks like:
<form>
<input type="hidden" name="isHidden" value="true" />
<p>
Name:
<input type="text" name="fName" value="" />
</p>
<p>
Mailing List:
<input type="radio" name="bMailingList" value="true" /> yes
<input type="radio" name="bMailingList" value="false" /> no
</p>
<p>
Work/Home:
<select name="place">
<option value="home">Home</option>
<option value="work">Work</option>
</select>
</p>
<input type="hidden" name="field1" value="true" />
<input type="hidden" name="field2" value="false" />
<input type="hidden" name="field3" value="true" />
</form>
NOTE:
It's not important what your field names are called, the code will
automatically add anything from the form scope that's not
found in the parsed content or that doesn't have the name
"fieldnames" (which is a special field created by ColdFusion.)
There are a few attributes in the tag. The exclude
attribute allows you to specify a list of fields you want to
ignore. This can be useful if there's extra information in your
form scope that should be ignored. The useNames
is designed to allow you to specify the case of the field—which
is important if you're using JavaScript to access the form fields
(JavaScript is case sensitive, and CF outputs form scope keys all
in upper case.)
2 Comments
Comments for this entry have been disabled.