Using CF to automatically pass form fields from page to page

Posted by Dan on Aug 1, 2006 @ 9:23 PM

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.)

Categories: HTML/ColdFusion, Source Code

2 Comments


Comments for this entry have been disabled.