Each: Unified Struct And Array Iteration In ColdFusion

Posted July 9, 2009 at 8:53 AM by Ben Nadel

Tags: ColdFusion

The other day, I was talking to Marc Esher on Twitter about creating better StructFindValue() functionality in ColdFusion. Specifically, he wanted to add regular expression searching such that a target value could simply match a pattern rather than an entire string. I decided to see if I could build the desired functionality and quickly found that the requirement of recursively crawling over both structs and arrays lead to a whole bunch of duplicated code and logic.

After thinking it through, I figured the easiest way to fix this duplication would be to create a ColdFusion custom tag that could provide a uniform interface for looping over both structs and arrays (much like the .each() method in jQuery). This way, both the struct and array iteration logic could be reduced to a single loop rather than two separate loops. As for the tag interface, I decided to use that of a collection loop, where "Item" is the iteration variable and "Collection" is the struct or array over which we are iterating. I chose the collection interface because it felt the most generic of the two (struct vs. array).

For each iteration of the collection, Item (the iteration variable) holds a struct with the following values:

  • CollectionType: The type of collection, Array or Struct, over which we are iterating. This might seem like useless meta data, but I figured it might come in handy depending on the context.
  • Index: The index of the current iteration, starting with one.
  • Key: The key within the given collection being examined within the current iteration. For struct iteration, this would be the struct key. For array iteration, this would be the array index.
  • Value: The value within the given collection located at the current key.

The code for this ColdFusion custom tag is actually quite straightforward, requiring little more than a few CFIF statements.

Each.cfm

  • <!--- Check to see which mode we are executing. --->
  • <cfif (thistag.executionMode eq "start")>
  •  
  • <!--- Param tag attributes. --->
  •  
  • <!---
  • This is the "index" variable name that the caller will
  • be using to store the iterative value. I went with item
  • rather than index only because this can iterate over
  • non-array items.
  • --->
  • <cfparam name="attributes.item" type="variablename" />
  •  
  • <!---
  • This it the collection that we are iterating over. This
  • might be an array, a struct, etc.
  • --->
  • <cfparam name="attributes.collection" type="any" />
  •  
  •  
  • <!--- Start tag logic. --->
  •  
  •  
  • <!--- Check to see if we have a valid collection type. --->
  • <cfif (
  • !isStruct( attributes.collection ) &&
  • !isArray( attributes.collection )
  • )>
  •  
  • <!--- Invalid collection type. Throw error. --->
  • <cfthrow
  • type="UnsupportedCollectionType"
  • message="Unsupported collection type."
  • detail="Unsupported collection type. Currently, only structures and arrays are supported."
  • />
  •  
  • </cfif>
  •  
  •  
  • <!---
  • ASSERT: At this point, we know that our collection is
  • a valid type for what this custom tag can handle.
  • --->
  •  
  •  
  • <!---
  • Check to see if we have any values in our
  • collection. If not, then we can immediately break
  • out of the custom tag.
  • --->
  • <cfif (
  • (
  • isStruct( attributes.collection ) &&
  • !structCount( attributes.collection )
  • ) ||
  • (
  • isArray( attributes.collection ) &&
  • !arrayLen( attributes.collection )
  • ))>
  •  
  • <!--- Collection is empty. --->
  • <cfexit method="exittag" />
  •  
  • </cfif>
  •  
  •  
  • <!---
  • ASSERT: At this point, we know that our collection has
  • at least ONE item in the collection.
  • --->
  •  
  •  
  • <!--- Create a variable to handle the loop iteration. --->
  • <cfset iterationIndex = 1 />
  •  
  • <!---
  • Check to see what kind of data collection we have.
  • Each collection type will have to be handled slightly
  • differently.
  • --->
  • <cfif isStruct( attributes.collection )>
  •  
  • <!--- Get the array of struct keys. --->
  • <cfset keys = structKeyArray( attributes.collection ) />
  •  
  • <!---
  • Set up the item in the caller for the first
  • collection iteration.
  • --->
  • <cfset caller[ attributes.item ] = {
  • index = iterationIndex,
  • key = keys[ iterationIndex ],
  • value = attributes.collection[ keys[ iterationIndex ] ],
  • collectionType = "struct"
  • } />
  •  
  • <cfelseif isArray( attributes.collection )>
  •  
  • <!---
  • Set up the item in the caller for the first
  • collection iteration.
  • --->
  • <cfset caller[ attributes.item ] = {
  • index = iterationIndex,
  • key = iterationIndex,
  • value = attributes.collection[ iterationIndex ],
  • collectionType = "array"
  • } />
  •  
  • </cfif>
  •  
  • <cfelse>
  •  
  • <!--- Increment the iteration index for the next loop. --->
  • <cfset iterationIndex++ />
  •  
  • <!---
  • At this point, we have to check to see if our collection
  • requires any more iterations. For this, we again will need
  • to see what type of collection we are walking.
  • --->
  • <cfif (
  • (
  • isStruct( attributes.collection ) &&
  • (arrayLen( keys ) LT iterationIndex)
  • ) ||
  • (
  • isArray( attributes.collection ) &&
  • (arrayLen( attributes.collection ) LT iterationIndex)
  • ))>
  •  
  • <!--- We are done walking the collection. --->
  • <cfexit method="exittag" />
  •  
  • </cfif>
  •  
  •  
  • <!---
  • ASSERT: At this point, we know that we have at least one
  • more collection iteration remaining.
  • --->
  •  
  •  
  • <!---
  • Check to see which type of collection we have and
  • therefore how to update the item key for the next
  • iteration.
  • --->
  • <cfif isStruct( attributes.collection )>
  •  
  • <!---
  • Set up the item in the caller for the next
  • collection iteration.
  • --->
  • <cfset caller[ attributes.item ] = {
  • index = iterationIndex,
  • key = keys[ iterationIndex ],
  • value = attributes.collection[ keys[ iterationIndex ] ],
  • collectionType = "struct"
  • } />
  •  
  • <cfelseif isArray( attributes.collection )>
  •  
  • <!---
  • Set up the item in the caller for the next
  • collection iteration.
  • --->
  • <cfset caller[ attributes.item ] = {
  • index = iterationIndex,
  • key = iterationIndex,
  • value = attributes.collection[ iterationIndex ],
  • collectionType = "array"
  • } />
  •  
  • </cfif>
  •  
  •  
  • <!---
  • If we've gotten this far, it's time to loop back to the
  • tag body with the new item value.
  • --->
  • <cfexit method="loop" />
  •  
  • </cfif>

To test this ColdFusion custom tag, I set up a simple script that creates an array and a struct and then uses the tag to iterate over each:

  • <!--- Create an array. --->
  • <cfset myArray = [
  • "Tricia",
  • "Joanna",
  • "Molly"
  • ] />
  •  
  • <!--- Create a struct. --->
  • <cfset myStruct = {
  • Tricia = "Athletic",
  • Joanna = "Full Figured",
  • Molly = "Petite"
  • } />
  •  
  •  
  • <strong>Each: Array Iteration</strong><br />
  • <br />
  •  
  • <cf_each
  • item="index"
  • collection="#myArray#">
  •  
  • <cfdump
  • var="#index#"
  • label="Array Iteration"
  • />
  •  
  • <br />
  •  
  • </cf_each>
  •  
  •  
  • <strong>Each: Struct Iteration</strong><br />
  • <br />
  •  
  • <cf_each
  • item="index"
  • collection="#myStruct#">
  •  
  • <cfdump
  • var="#index#"
  • label="Struct Iteration"
  • />
  •  
  • <br />
  •  
  • </cf_each>

As you can see, the Each.cfm ColdFusion custom tag provides a unified interface for looping over both the array and the struct. When we run the above code, we get the following page output:

 
 
 
 
 
 
Each: ColdFusion Custom Tag Iteration For Struct And Array Iteration With A Unified Interface. 
 
 
 

ColdFusion already has the CFLoop tag which allows us to loop over both structs and arrays quite easily. But each form of the tag has its own interface. In most cases this is not a problem. But, in the few cases where this separation of interfaces leads to logic and code duplication, I think this Each.cfm ColdFusion custom tag will allow me to create more succinct, more maintainable code.




Reader Comments

Jul 9, 2009 at 9:33 AM // reply »
66 Comments

interesting approach Ben. nice job!


Jul 9, 2009 at 10:17 AM // reply »
11,246 Comments

@Steve,

Thanks my man. I actually built this tag to simplify the topic of my next blog post:

http://www.bennadel.com/blog/1635-REStructFindValue-Adding-Regular-Expression-Searching-To-StructFindValue-.htm


Jul 9, 2009 at 11:15 AM // reply »
2 Comments

Well done. I've use jQ's .each() function many times, and I've always thought it was a little bit of a pain to loop in CF. Thanks for this, hopefully I can find a place for it.


Jul 12, 2009 at 1:32 AM // reply »
1 Comments

Very neat idea for creating a better StructFindValue() functionality in ColdFusion, thanks!


Jul 12, 2009 at 5:28 PM // reply »
11,246 Comments

@Lionhart,

Yeah, jQuery's each() method is the best.

@Sue,

Thanks. I was inspired by Marc Esher.


Sep 11, 2009 at 12:55 PM // reply »
19 Comments

what we i have nested structures like

structure1
it has two elements
structure2
structure3
structure4

need only to output contents on structur 2 or 3

then how can we do that


Sep 12, 2009 at 9:54 PM // reply »
11,246 Comments

@Misty,

How do you need to output them? You should be able to reference the structs 2/3 directly.


Sep 12, 2009 at 11:10 PM // reply »
19 Comments

Hi Ben!

i know how to output structs if they are one level deep.

i have also code for them if they are one elvel deep like this:

<cfloop list="#StructKeyList(key)#" index="k">
<cfoutput>Value: #k#</cfoutput>
</cfloop>

my structures are like this:

STRUCTS:
sysFonts
STRUCTS
acaslonpro-bold
STRUCT
Acaslonpro-Bold
Struct
Description
fonttype
location
psname

while i just need to extract the fontName of every font listed in coldfusion 8.

after this i want them to be in Comma Separed list to store as text


Sep 21, 2009 at 8:36 AM // reply »
11,246 Comments

@Misty,

You need to set the collection of your structs to be nested struct. Something like:

<cfloop item="fontKey" collection="#XYZ.SysFonts#">

Once you are looking in the iteration of system font structures, you just need to grab the values of the location (or whatever you are trying to grab).

XYZ.SysFonts[ fontKey ].Location


Jan 27, 2012 at 1:20 PM // reply »
3 Comments

@Ben,

Great!

XYZ.SysFonts[ fontKey ].Location

Did exactly what I was looking for. I can now fix the dent in the wall from me banging my head against.

Thankx Bro


Post A Comment

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.

Please review the following issues:

Author Name:


Author Email:

Author Website:

Comment:

Supported HTML tags for formatting: <strong>bold</strong>   <em>italic</em>   <code>code</code>







  • Help Wanted - Find Your Next ColdFusion Job
Ben Nadel's Company - Epicenter Consulting Recent Blog Comments
May 24, 2013 at 5:39 PM
Ask Ben: Manually Enforcing Basic HTTP Authorization In ColdFusion
@Adam Oops! My mistake! I hadn't gotten that far in my testing - I'm still baby stepping my way through the process. ... read »
May 24, 2013 at 5:13 PM
Ask Ben: Manually Enforcing Basic HTTP Authorization In ColdFusion
Hi Jason, Thanks for checking up on that, but I still stand firm on my position. :) There are actually two listLast()'s in use, and you're right that the one using a space as a delimiter is fine. ... read »
May 24, 2013 at 4:45 PM
Ask Ben: Manually Enforcing Basic HTTP Authorization In ColdFusion
@Ben I have been lurking your site for quite some time, and haven't stepped up to comment until today. Thanks for all the great info - keep it up! @Adam I believe you are mistaken... as the commen ... read »
May 24, 2013 at 11:21 AM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
@WebManWalking, Ha ha, let's us never speak of justifying "##" notation again :P ... read »
May 24, 2013 at 11:18 AM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
@Ben, Ah, so it was indeed how I vaguely remembered it to be: A direct assignment value = users.id[ i ] causes value to retain the sticky datatype of the query column. Although unnecessary in ... read »
May 24, 2013 at 9:11 AM
Preventing Links In Standalone iPhone Applications From Opening In Mobile Safari
@Brandon, Hi, No, I haven't been able to do that. I have just kept it as it is. ... read »
May 23, 2013 at 9:52 PM
Preventing Links In Standalone iPhone Applications From Opening In Mobile Safari
@Muhmmadibn Did you figure out a solution to launching PDFs? I am running into the same issues myself. There is no way to close the PDF or go back once you launch it. Thanks in advance! ... read »
May 23, 2013 at 6:06 PM
The Girl Who Broke My Heart, And Made Me A Better Person
Good day,ladies and gentle men, my name is Dr AMADI the great spell caster in Africa, i have help so many people for different kind of problems,who say there is no solution to problems on earth, that ... read »
InVision App - Prototyping Made Beautiful With Prototyping Tools