Each: Unified Struct And Array Iteration In ColdFusion

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

For Cut-and-Paste