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.Post A Comment — ❤️ I'd Love To Hear From You! ❤️
Post a Comment →