Normalizing Collection Entries In ColdFusion
In Lucee CFML, looping over collections is effortless because Lucee exposes both the key
and value
attributes on the CFLoop
tag. Furthermore, you can even use struct
instead of collection
for a more intuitive read on the code. Unfortunately, Adobe ColdFusion hasn't caught up with these ergonomics niceties just yet. As such, I created a user defined function (UDF) that normalizes collections (Structs and Arrays) into an array that always exposes index
, key
, and value
.
The toEntries()
UDF is straightforward—it takes a collection and then uses .map()
to return a new array that has a more comprehensive set of properties:
<cfscript>
/**
* I return the entries for the given collection (Array or Struct). Each entry is a
* struct with a normalized set of keys: index, key, value.
*/
public array function toEntries( required any collection ) {
if ( isArray( collection ) ) {
return collection.map(
( value, i ) => {
return {
index: i,
key: i,
value: value
};
}
);
}
if ( isStruct( collection ) ) {
return collection.keyArray().map(
( key, i ) => {
return {
index: i,
key: key,
value: collection[ key ]
};
}
);
}
throw(
type = "UnsupportedCollectionType",
message = "Entries cannot be taken from unsupported collection type."
);
}
</cfscript>
When dealing with an array, it's a little quirky that the index
and key
values are both the iteration offset. But, this keeps it consistent with the struct iteration, which does differentiate between the struct key and the iteration offset.
To see this in action, let's dump-out the entries for both a struct and an array:
<cfscript>
include "./utils.cfm";
writeDump(
label = "From Struct",
var = toEntries({
"foo": "bar",
"hello": "world",
"fizz": "buzz"
})
);
writeDump(
label = "From Array",
var = toEntries([
"Just",
"Do",
"It"
])
);
</cfscript>
When we run this ColdFusion code, we get the following page output:
As you can see, the toEntries()
function exposed the same set of keys when consuming either the struct or the array.
To be clear, this isn't a function that I would use all the time. It might not even be a function that I use very often. But, it can be helpful when generating HTML output. This is especially true when linking off to other pages from a complex data structure&madsh;having the index
, the key
, and the value
can making formatting the HTML and generating href
attributes easier.
For example, having the index
makes zebra-stripping easier. In the following ColdFusion code, each iteration receives an optional CSS class based on its iteration offset.
<cfscript>
include "./utils.cfm";
data = {
"foo": "bar",
"hello": "world",
"fizz": "buzz",
"spicy": "meatball"
};
</cfscript>
<cfoutput>
<style type="text/css">
.odd {
background-color: ##f0f0f0 ;
}
</style>
<cfloop index="entry" array="#toEntries( data )#">
<p class="<cfif ( entry.index % 2 )>odd</cfif>">
#encodeForHtml( entry.key )#
→
#encodeForHtml( entry.value )#
</p>
</cfloop>
</cfoutput>
Here, we're using the modulo operator to apply the odd
CSS class to odd rows in the output. And, when we run this ColdFusion code, we get the following output:
One day, I hope that Adobe ColdFusion updates their CFLoop
tag and makes this toEntries()
function superfluous. But, for the time being, this function makes my life easier.
Want to use code from this post? Check out the license.
Reader Comments
15-years ago, I tried to do the same thing using a custom tag:
www.bennadel.com/blog/1634-each-unified-struct-and-array-iteration-in-coldfusion.htm
I would use a pre CSS solution for zebra-striping 😁
@Scott, ha ha, yeah, probably the right move for CSS (especially with all the
nth
child stuff we have these days).Post A Comment — ❤️ I'd Love To Hear From You! ❤️
Post a Comment →