Skip to main content
Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.

Managing Lists Of IDs Using HTML FORM Posts In Lucee CFML 5.3.7.47

By Ben Nadel on
Tags: ColdFusion

At InVision, I'm in the middle of building a custom feature for one of our clients. This feature lives outside of any SPA (Single-Page Application); and, uses "old school" form submission techniques to process the interactions. The techniques that I'm using are the same ones that I learned 2 decades ago. And they still work perfectly well! This is a testament to both the HTML specification and the way that ColdFusion simplifies the management of form submission data. And while this feels like "old technology" to me, it occurred to me that there might be little gems in here that newer developers don't know about. As such, I wanted to put together a quick demo on managing lists of IDs using HTML FORM posts in Lucee CFML 5.3.7.47.

Before we jump into the full demo, I think we need to build up some foundational understanding of the interplay between an HTML FORM post and the way in which ColdFusion exposes form submission data to the server-side processing. The most basic concept here is that multipart form-data is automatically parsed into the form scope which we can then access as a set of key-value pairs.

By default, each name-value pair for inputs in the HTML FORM post shows up as a unique key-value pair in the form scope. However, if you submit two inputs with the same name, the ColdFusion server will collapse those values down into a single list. To see this in action, let's try submitting an HTML FORM with several hidden inputs that all have the same name:

<cfoutput>

	<form method="post" action="#cgi.script_name#">
		<!---
			These form fields will be submitted to the server as individual fields.
			However, since they all have the SAME NAME, the ColdFusion server will
			automatically collapse them down into a single, comma-delimited list.
			--
			NOTE: There is an Application.cfc setting to change the default behavior from
			a LIST to an ARRAY (sameFormFieldsAsArray); but, I have not tried this.
		--->
		<input type="hidden" name="userIdList" value="1" />
		<input type="hidden" name="userIdList" value="2" />
		<input type="hidden" name="userIdList" value="3" />
		<input type="hidden" name="userIdList" value="4" />
		<input type="hidden" name="userIdList" value="5" />

		<button type="submit">
			Post User IDs
		</button>

		<a href="#cgi.script_name#">
			Clear
		</a>
	</form>

	<cfdump
		label="FORM.userIdList"
		var="#( form.userIdList ?: '' )#"
	/>

</cfoutput>

As you can see, we have five hidden inputs all using the name, userIdList, but each with a different value. And, when we submit this HTML FORM, we get the following output:

Multiple form fields are collapsed into a single form-scope LIST entry in Lucee CFML.

From the network activity, we can see that each of the hidden inputs was submitted as a separate form field in the multipart form-data payload. However, on the ColdFusion side, those form fields were all collapsed down into a single, comma-delimited list of the same name.

Apparently, there is an Application.cfc (ColdFusion Framework Component) setting that changes the default "collapsing" behavior to use an Array instead of a List:

this.sameFormFieldsAsArray = true

ASIDE: There is a similar setting for URL parameters, sameUrlFieldsAsArray.

I have not used this because I work primarily with brownfield applications and changing this setting at this point in time would almost certainly break something in my ColdFusion applications. That said, we can use special naming conventions to change this behavior on a per-form basis. By taking the above example and suffixing the hidden input names with [], it will signal to the ColdFusion server that the values should be coalesced into an Array, not a list:

<cfoutput>

	<form method="post" action="#cgi.script_name#">
		<!---
			These form fields will be submitted to the server as individual fields.
			However, since they all have the SAME NAME, the ColdFusion server will
			automatically collapse them down into a single ARRAY (since the names are
			all suffixed with "[]").
		--->
		<input type="hidden" name="userIdList[]" value="1" />
		<input type="hidden" name="userIdList[]" value="2" />
		<input type="hidden" name="userIdList[]" value="3" />
		<input type="hidden" name="userIdList[]" value="4" />
		<input type="hidden" name="userIdList[]" value="5" />

		<button type="submit">
			Post User IDs
		</button>

		<a href="#cgi.script_name#">
			Clear
		</a>
	</form>

	<cfdump
		label="FORM.userIdList"
		var="#( form.userIdList ?: [] )#"
	/>

</cfoutput>

As you can see, this example is exactly the same. Only, instead of the form fields being named, userIdList, they are named, userIdList[]. And now, when we submit this HTML FORM, we get the following output:

Multiple form fields are collapsed into a single form-scope ARRAY entry in Lucee CFML.

From the network activity, we can see that each of the hidden inputs was submitted as a separate form field in the multipart form-data payload. However, on the ColdFusion side - just as in the first example - those form fields were all collapsed down into a single form scope entry of the same name. Only, this time, that value is an array, not a list. Note that the [] suffix from the HTML FORM post is not part of the final form entry.

The next HTML FORM behavior that we need to understand for this demo is that Submit Buttons can provide form data of their own. In their most basic implementation, a submit button just triggers the post-back of the HTML FORM. However, if you defined name and value attributes on a submit button, that name/value pair will be submitted as an entry in the form post. But - and this is a critical point - a submit button's name/value pair will only be included if the user clicked on that submit button.

What this means is that we can include multiple submit buttons in a single form; and then, use the name/value pairs to determine which action the user actually took (when submitting the form):

<cfoutput>

	<form method="post" action="#cgi.script_name#">
		<!---
			When you design an HTML FORM, each submit button can have its own name-value
			pair. And, when you use the submit button to submit the form, the button's
			name-value pair is submitted along with the form data. As such, you can then
			use that name-value pair to determine which action the user took.
		--->
		<button type="submit" name="action" value="DoThis">
			Do This
		</button>

		<button type="submit" name="action" value="DoThat">
			Do That
		</button>

		<button type="submit" name="action" value="DoOther">
			Do Other
		</button>

		<a href="#cgi.script_name#">
			Clear
		</a>
	</form>

	<cfdump
		label="FORM.action"
		var="#( form.action ?: '' )#"
	/>

</cfoutput>

As you can see, we have three different submit buttons each with the name, action, but a different value attribute. Now, when we click on each one of the buttons, we get a different form.action value on the ColdFusion side of the workflow:

Each submit button submits a unque value in Lucee CFML.

As you can see, with each HTML FORM submission, our form.action entry has a different value.

Everything we've seen so far is "old school". This is how ColdFusion has worked for a really long time. And, this is how the HTML form specification has worked since for as long as I can remember. But, using these "old school" techniques, we can easily manage data.

To bring this all together, let's create a ColdFusion page that allows us to add and delete from a list of user IDs. You can imagine that this page would allow a customer to review and curate a list of data before some final processing step. Things of note in the following code:

  • We're using hidden inputs (all with the same name) to include the existing user IDs in each post-back.

  • We're using special [] suffix notation to collapse the like-named values into an Array.

  • We're using submit buttons (all with the same name) to remove targeted user IDs from the list.

  • We're wrapping the whole page in a single HTML FORM tag so that all the data is posted back for each action.

Note that for the sake of simplicity, we're not inspecting the data or validating it in any way. Nor are we worrying about duplicate values.

<cfscript>

	// Setup our default form field values.
	param name="form.newUserID" type="string" value="";
	param name="form.removeUserID" type="string" value="";
	param name="form.userIDs" type="array" default=[];

	// If a new ID was provided, add it to the collection!
	// --
	// NOTE: For the sake of simplicity, we are not going to do any special handling of
	// commas in the value. We're just assuming that this is a controlled environment
	// with valid inputs.
	if ( form.newUserID.len() ) {

		form.userIDs.append( form.newUserID );

	}

	// If a remove ID was provided, remove it from the collection!
	if ( form.removeUserID.len() ) {

		form.userIDs.delete( form.removeUserID );

	}

</cfscript>
<cfoutput>

	<form method="post" action="#cgi.script_name#">

		<!--- NEW userID related fields. --->
		<p>
			<input
				type="text"
				name="newUserID"
				value=""
				placeholder="New user ID..."
				autofocus
			/>

			<button type="submit">
				Add User ID
			</button>
		</p>

		<table border="1" cellpadding="10" cellspacing="2">
		<thead>
			<tr>
				<th> User ID </th>
				<th> <br /> </th>
			</tr>
		</thead>
		<cfloop value="id" array="#form.userIDs#">
			<tr>
				<td>
					#encodeForHtml( id )#
				</td>
				<td>
					<!---
						In order to make sure that the full set of user IDs is maintained
						across the various FORM POSTS, we are going to include each ID as
						a hidden input field. Then, on each form post, ColdFusion will
						collapse all of the like-named fields into a single value. And,
						since we using the "[]" suffix, that value will be an ARRAY.
					--->
					<input
						type="hidden"
						name="userIDs[]"
						value="#encodeForHtmlAttribute( id )#"
					/>

					<!---
						Each row in this table will have its own SUBMIT BUTTON. However,
						since they are using unique Name/Value pairs, we can easily
						determine which value the user was referring to (since we're
						submitting the target ID as the VALUE).
					--->
					<button
						type="submit"
						name="removeUserID"
						value="#encodeForHtmlAttribute( id )#">
						Remove
					</button>
				</td>
			</tr>
		</cfloop>
		</table>

		<p>
			<!---
				A no-op post back, just to demonstrate that all of the hidden inputs will
				correctly maintain the current list of IDs.
			--->
			<button type="submit">
				Post Back
			</button>

			<a href="#cgi.script_name#">
				Clear
			</a>
		</p>

	</form>

</cfoutput>

Now, if we interact with this HTML FORM, we get the following output:

A list of IDs being managed using form submission techniques in Lucee CFML.

As you can see, by using all of the techniques that we outlined above, we're easily able to manage a list of user IDs using HTML FORM posts. And, because we never submit the list of IDs in the URL, we never have to worry about data-length limits imposed by some browsers.

It's awesome how easy these "old school" techniques can be. And, it's always a pleasure to work with HTML FORM data in ColdFusion. And, hopefully, there was something in this post that was new and exciting to newer ColdFusion developers.



Reader Comments

Even us old school developers need a reminder how to use basic HTML sometimes. I'm so accustom to using JS to solve what <button value=''> solves that I've completely forgotten about it. Thanks for the reminder!

Reply to this Comment

@Chris,

Heck yeah, teamwork! This is how I feel every time I remember that there's like 100 "semantic" HTML elements that I don't know about 😂

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Blog
Live in the Now
Oops!
NEW: Some basic markdown formatting is now supported: bold, italic, blockquotes, lists, fenced code-blocks. Read more about markdown syntax »
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.