Polyfill Form Field Grouping Using Bracket Notation In Adobe ColdFusion
One of the small features that I absolutely love in Lucee CFML is the ability to group form fields as an array by suffixing the form field names with []
. As in name="tags[]"
. When a group of related form fields have this same name, Lucee CFML will automatically aggregate the field values as an array and remove the []
suffix from the field name (in the form
scope). Unfortunately, Adobe ColdFusion doesn't offer this behavior. But, we can polyfill it at the top of each request.
As both Ryan Stille and Raymond Camden have previously written about, the raw form post data can be accessed via the getPageContext()
function:
getPageContext().getRequest().getParameterMap()
According to the Java docs, the getParameterMap()
function returns an immutable struct of the type:
java.util.Map<java.lang.String, java.lang.String[]>
In this data structure, every single form value is represented as an Array of strings. Most of these arrays contain a single value. However, if the submitted form has two fields with the same name, both of their values show up in the same Array. Which is exactly what we want. In order to polyfill the behavior, all we have to do is grab that underlying Array.
In the following Application.cfc
ColdFusion application framework component, I'm calling the method polyfillFieldGrouping()
in the onRequestStart()
event handler. This will ensure that it's called on every single incoming request. Internally, this method is normalizing the form field key (removing the []
suffix) and is converting the string[]
array into a native ColdFusion array:
component | |
output = false | |
hint = "I define the application settings and event handlers." | |
{ | |
// Define the application settings. | |
this.name = "PolyfillFormFieldGrouping"; | |
this.applicationTimeout = createTimeSpan( 1, 0, 0, 0 ); | |
this.sessionManagement = false; | |
this.setClientCookies = false; | |
this.passArrayByReference = true; | |
// --- | |
// PUBLIC METHODS. | |
// --- | |
/** | |
* I initialize each inbound HTTP request. | |
*/ | |
public void function onRequestStart() { | |
polyfillFieldGrouping( form ); | |
} | |
// --- | |
// PRIVATE METHODS. | |
// --- | |
/** | |
* I polyfill the "field[]" form parameter grouping behavior in Adobe ColdFusion. | |
*/ | |
private void function polyfillFieldGrouping( required struct formScope ) { | |
// If the fieldNames entry isn't defined, the form scope isn't populated. | |
if ( ! formScope.keyExists( "fieldNames" ) ) { | |
return; | |
} | |
// The parameter map gives us every form field as an array. | |
var rawFormData = getPageContext() | |
.getRequest() | |
.getParameterMap() | |
; | |
for ( var key in rawFormData.keyArray() ) { | |
if ( key.right( 2 ) == "[]" ) { | |
// Remove the "[]" suffix. | |
var normalizedKey = key.left( -2 ); | |
// The underlying Java value is of type, "string[]". We need to convert | |
// that value to a native ColdFusion array (ArrayList) so that it will | |
// behave like any other array, complete with member methods. | |
var normalizedValue = arrayNew( 1 ) | |
.append( rawFormData[ key ], true ) | |
; | |
// Swap the form scope key-value pairs with the normalized versions. | |
formScope[ normalizedKey ] = normalizedValue; | |
formScope.delete( key ); | |
} | |
} | |
// Clean-up list of field names (removing [] notation). | |
formScope.fieldNames = formScope.fieldNames | |
.reReplace( "\[\](,|$)", "\1", "all" ) | |
; | |
} | |
} |
The polyfillFieldGrouping()
method iterates over the underlying form data. And, for any key that ends in []
, it removes the []
suffix, converts the value to a native ColdFusion array, and merges it into the native form
scope. Now, any field that is submitted with the []
suffix is automatically grouped in the form
scope:
<form method="post"> | |
<input type="hidden" name="name" value="Total Recall" /> | |
<input type="hidden" name="description" value="A family takes a whacky vacation to Mars." /> | |
<!--- These will be grouped together as a list (default behavior). ---> | |
<input type="hidden" name="ids" value="1" /> | |
<input type="hidden" name="ids" value="3" /> | |
<input type="hidden" name="ids" value="29" /> | |
<!--- These will be grouped together as an array (polyfill behavior). ---> | |
<input type="hidden" name="tags[]" value="fun" /> | |
<input type="hidden" name="tags[]" value="action" /> | |
<input type="hidden" name="tags[]" value="adventure" /> | |
<input type="hidden" name="tags[]" value="scifi" /> | |
<!--- These will be grouped together as an array (polyfill behavior). ---> | |
<input type="hidden" name="actors[]" value="Schwarzenegger, Arnold" /> | |
<input type="hidden" name="actors[]" value="Stone, Sharon" /> | |
<button type="submit"> | |
Submit | |
</button> | |
</form> | |
<cfdump var="#form#" label="Form Scope" /> |
As you can see, there are multiple form fields with the names tags[]
and actors[]
. Upon submission, these fields get grouped into an array:

How great is that! And, it works when only a single field is present; which makes it more effective and predictable than the application setting, sameFormFieldsAsArray
.
This kind of grouping makes it easier to start working with more complex form data. And now, the feature (can work) the same in both Lucee CFML—which supports it natively—and Adobe ColdFusion—which can be polyfilled.
Want to use code from this post? Check out the license.
Reader Comments
CFWheels has done this for years and is one of the form features I've most appreciated. Well, maybe not most, but have very much appreciated 🤪
https://cfwheels.org/
@Chris,
I've always heard good things about CFWheels - it's based heavily on the Ruby on Rails mentality of building sites, I think. I haven't tried it myself.
I use CFWheels as well for all of our forms/sites. I have to create a lot of forms with sections that allow the user to add unlimited rows of a grouping of fields. The input names are quite long, but look something like "input name="project[subaward][#subawardcounter#][subawardname]" etc... I'll do a cfloop to build up the current data to the page, and then use javascript to build up another row of data when they click "Add New". When it all gets saved CFWheels does a pretty job of just saving it, although I usually just loop over and save it anyways. The nice part is when there's a form error the data returned from CFWheels to populate the form matches, so it's no extra work.
CFWheels treats all of this form data like a struct. Even parts that seem like it should be an array. I actually messed around with sameFormFieldsAsArray last week, but ultimately decided to stick with my struct approach that matches CFWheels.
Haven't seen a better approach really yet. Wish I didn't have to build it all up in Coldfusion and Javascript, but the end result is nice.
@Mike,
For me, this is always the tension between leaning on server-side processing vs. client-side processing. When building a single-page app style form, this kind of stuff is significantly easier because you can just manufacture some large, arbitrary object tree and then submit it to the server as JSON. But, of course, you then need to have all the SPA-dependencies in place.
Conversely, building large, complex forms using POST-backs to the server means fewer dependencies and setup; but, also more complexity with constructing and processing of the data.
That said, it sounds like CFWheels does some nice lifting for you to remove some of this friction.
You can set
this.sameFormFieldsAsArray = true
in Application.cfc since ACF10 to accomplish this. No brackets required on the form names.@Rodney,
The
sameFormFieldAsArray
setting is good if you know that you're going to have more than one field with the same name. The problem comes about when you may have instances with only a single value. Imagine a list of checkboxes with the namesportID
. If the user only checks one box, then the form value comes through a string. However, if the user checks two or more boxes, then and only then doe the form value come through as an array (of strings).In that case, it's hard to
param
a form field:param name="form.sportID" type="array";
... since it is only going to be an array some of the time.
The bracket-notation,
sportID[]
, on the other hand, will always come through as an array even when only a single checkbox is checked. This is part of the power - the predictable access and param'ing pattern.Can't believe ColdFusion STILL doesn't support this the way Lucee does because the bracket format is very commonly used in such a wide array of languages, and is by far the easiest way to handle some complex form validations as well. The CF setting can also be an issue on legacy sites where you may not know if there are other places in a site where the original handling of same field names is needed, while the bracket notation is very clear in what it is intending to do.
And as per usual, you saved me a lot of time Ben, figuring out how to deal with this issue myself, as I had multiple sets of 3 related fields being passed to the form handler, but one of them is allowed to be blank. Something that obviously ColdFusion's way of (not) handling the blank ones is giving me major headaches!
@Mary Jo,
Yeah, it's crazy that this isn't supported yet! And, after you left this comment, it got me thinking about the support for this in the URL scope. I don't think I ever tried this before, but
parameter[]
works in the URL for Lucee CFML as well. Of course, also not supported in Adobe ColdFusion. Which then sent me down a rabbit hole on trying to polyfill the URL support as well. It turns out that Lucee and ACF both have different API for the underlyinggetPageContext().getRequest()
implementation. It's definitely possibly to polyfill the URL, just trying to figure out a nice look-and-feel for it.After MJ's comment last week, this got me thinking about the URL scope and whether or not the same technique works. Turns out, this also works natively in Lucee CFML. And, I was able to figure out how to polyfill it in Adobe ColdFusion as well:
www.bennadel.com/blog/4827-polyfill-url-search-parameter-grouping-using-bracket-notation-in-adobe-coldfusion.htm
It's gets a little tricky since the URL parameters show up in different places in the underlying request based on GET vs. POST.
Post A Comment — ❤️ I'd Love To Hear From You! ❤️