Each: Unified Struct And Array Iteration In ColdFusion

Posted July 9, 2009 at 8:53 AM

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

 Launch code in new window » Download code as text file »

  • <!--- 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:

 Launch code in new window » Download code as text file »

  • <!--- 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.

Download Code Snippet ZIP File

Post Comment  |  Ask Ben  |  Permalink  |  Other Searches  |  Print Page




Learning ColdFusion 9 - ColdFusion 9 tutorials, samples, examples, demos

Reader Comments

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

interesting approach Ben. nice job!


Jul 9, 2009 at 10:17 AM // reply »
6,371 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 »
6,371 Comments

@Lionhart,

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

@Sue,

Thanks. I was inspired by Marc Esher.


Misty
Sep 11, 2009 at 12:55 PM // reply »
10 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 »
6,371 Comments

@Misty,

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


Misty
Sep 12, 2009 at 11:10 PM // reply »
10 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 »
6,371 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


Post Comment  |  Ask Ben

Recent Blog Comments
Nov 7, 2009 at 5:53 PM
Ask Ben: Javascript String Replace Method
You can find here an advanced function that prepared with javascript replace function. This can make the first letters of words, sentences, lines and whatever you define automatically: http://www.m ... read »
Andrew Neely
Nov 7, 2009 at 4:56 PM
A Moment That Touched Me - The Fountainhead
Ben, Glad you enjoyed the podcast. Yeah, the Tank Riot guys can get really chatty during the episodes, but that's part of the charm of it for me. They've covered everything from Nichola Tesla to Cha ... read »
Nov 7, 2009 at 4:43 PM
Building A Fixed-Position Bottom Menu Bar (ala FaceBook)
Is it possible to make some more MenĂ¼`s ? ... read »
Jill
Nov 7, 2009 at 11:40 AM
How To Unformat Your Code (Like A Pro)
Derek, I think you might be right - sweet! Thanks for the link :) ... read »
Nov 7, 2009 at 11:25 AM
How To Unformat Your Code (Like A Pro)
I think it would be way easier to just use this http://www.logichammer.com/html-formatter/ He just released v3 and it rocks. ... read »
Jill
Nov 7, 2009 at 7:58 AM
How To Unformat Your Code (Like A Pro)
LMAO - this was pretty funny! I have to admit - I also love to reformat code so I can read it. My boss used to tell me to leave my OCD at home. Now I don't feel so bad after reading everyone else' ... read »
Nov 6, 2009 at 10:10 PM
How To Unformat Your Code (Like A Pro)
The timing of this post is just uncanny. I spent the last 15-20 minutes manually un-formatting my "Ben Nadel" style code within a CFC of mine. I was really digging the readability a few weeks ago, bu ... read »
Roe
Nov 6, 2009 at 5:11 PM
Passing Arrays By Reference In ColdFusion - SWEEET!
ArraySort also reorders the results of these java obj's ... read »