Skip to main content
Ben Nadel at cf.Objective() 2017 (Washington, D.C.) with: Mike Sprague
Ben Nadel at cf.Objective() 2017 (Washington, D.C.) with: Mike Sprague ( @spraguey )

Breaking Out Of A ColdFusion Custom Tag Loop

By on
Tags:

Yesterday, I was messing with ColdFusion custom tags when I wondered if there was any way to break out of a loop created using ColdFusion custom tags and the CFExit tag:

<cfexit method="loop" />

After a few minutes of coding, I discovered that the CFBreak tag throws an error and that all of the CFExit variations, when used in the calling page, cause processing to completely stop in the current template (not the custom tag loop).

Ok, so clearly, there was no inherent way to break out of a ColdFusion custom tag loop; so, what would be the cleanest way to create such functionality. I considered adding some sort of "continue" condition attribute that could be added to the tag. I also considered creating a child tag that could signal the break to the parent tag:

<loop:break />

But, both of these methodologies required me to add more stuff to the tag. I wanted to try and do this without any extras. That's when it occurred to me - what if we could signal the ending of the loop by destroying the index variable. That's when I came up with this:

Start Loop<br />

<!--- Loop 10 times using ColdFusion custom tag. --->
<cf_loop10 index="REQUEST.Index">

	#REQUEST.Index#<br />


	<!---
		On the fifth iteration, break out of the loop by
		destroying the index variable; this will signal
		to the ColdFusion custom tag that we don't want
		to iterate again.
	--->
	<cfif (REQUEST.Index EQ 5)>

		<cfset StructDelete( REQUEST, "Index" ) />

	</cfif>

</cf_loop10>

End Loop<br />

As you can see above, on the fifth iteration of the loop, we are deleting the Index variable from the REQUEST scope. This should signal to the ColdFusion custom tag that the loop is ending. And, in fact, when we run the above code, we get the following output:

Start Loop
1
2
3
4
5
End Loop

It works, but of course, it doesn't work without a little elbow grease; we have to update the ColdFusion custom tag loop to abide by this index-deletion agreement:

<!--- Check to see which mode we are executing. --->
<cfif (THISTAG.ExecutionMode EQ "Start")>

	<!--- Param the index attribute. --->
	<cfparam
		name="ATTRIBUTES.Index"
		type="variablename"
		/>

	<!--- Set the loop index. --->
	<cfset VARIABLES.Index = 1 />


	<!---
		Store the current index in the caller scope so that
		the calling page can access the current loop iteration.
	--->
	<cfset CALLER[ ATTRIBUTES.Index ] = VARIABLES.Index />

<cfelse>

	<!--- Increment the index. --->
	<cfset VARIABLES.Index++ />

	<!---
		Check to see if we have another loop to do. This will be
		true if we have not completed all 10 iterations AND our
		CALLER variable still exists. If the CALLER variable was
		removed, that will signal the breaking out of the loop.

		NOTE: We know that this value will exist at least for the
		first loop iteration.
	--->
	<cfif (
		(VARIABLES.Index LTE 10) AND
		StructKeyExists( CALLER, ATTRIBUTES.Index )
		)>

		<!---
			We are still in our 10 loop interations. Store the
			index in the caller and loop back.
		--->
		<cfset CALLER[ ATTRIBUTES.Index ] = VARIABLES.Index />

		<!--- Loop to next iteration. --->
		<cfexit method="loop" />

	<cfelse>

		<!---
			We have completed all the iterations or been
			signaled to stop (via the destruction of the
			index key) so break out of this looping.
		--->
		<cfexit method="exittag" />

	</cfif>

</cfif>

This works, but is definitely a bit of a sloppy hack. For starters, because our stop-logic is in the End tag logic of the ColdFusion custom tag, we know that the custom tag loop will execute at least once, no matter what. Furthermore, because the "break" here depends on the existence of a variable at the time of end-tag execution, its not a hard break; this means that the even if you destroy the index variable at the beginning of the loop logic, the rest of the code within the loop will execute.

So, it's not perfect, but I thought I would throw it out there in case it helped anyone.

Want to use code from this post? Check out the license.

Reader Comments

354 Comments

I think I may be missing something. If you want to stop looping, why not just not output the cfexit/loop tag? That signals CF to repeat the custom tag, so just don't output it. Again though I think I'm missing something.

15,674 Comments

@Ray,

That's basically what I am doing. From within the custom tag, if the "index" variable does not exist in the CALLER scope, I am not using the CFExit/Loop tag, but rather the CFExit/ExitTag tag.

What I was trying to do was to find a way for the calling page to "signal" to the custom tag to perform this logical choice.

67 Comments

Hi Ben.
This looked like a sitter for a nested tag, and getting the nested tag to communicate with the parent tag via <cfassociate>. As I had never used <cfassociate> before, I decided it was a good opportunity to investigate further.

Here's what 15-odd min of investigation lead me to:

<!--- caller.cfm --->
<cf_loop iterations="10" index="i">
<cfoutput>[#i#]</cfoutput>Hello World<br />
<cfif i gt 5>
<cf_break>
</cfif>
</cf_loop>

<!--- loop.cfm; based on your one--->
<cfif structKeyExists(thistag, "breakAttributes") and thistag.breakAttributes[1].break>
<cfset thistag.generatedContent = "">
<cfexit method="exittag">
</cfif>

<cfif (THISTAG.ExecutionMode EQ "Start")>

<cfparam name="attributes.index" type="variablename">
<cfparam name="attributes.iterations" type="integer">

<cfset variables.index = 1>
<cfset caller[attributes.index] = variables.index>
<cfelse>
<cfset variables.index = variables.index + 1>
<cfif variables.index lte attributes.iterations>
<cfset caller[attributes.index] = variables.index>
<cfexit method="loop">
<cfelse>
<cfexit method="exittag">
</cfif>

</cfif>

<!--- break.cfm --->
<cfset attributes.break = true>
<cfassociate basetag="cf_loop" datacollection="breakAttributes">

Sorry if I'm putting the associate logic in the wrong place, like I said, this is the first time I've had call to use the tag, so am just trying follow livedocs, and make some sense out of what they say (the examples are pitiful).

--
Adam

15,674 Comments

@Adam,

That looks pretty good. Nice proof of concept. As far as the CFAssociate tag, I am not sure if it matters where it goes exactly; I tend to put it as one of the first things in the tag.

If you are looking into nested tags, definitely take a look at the GetBaseTagData( "PARENT_TAG_NAME" ) function. This function allows the child tag to "reach" into the data of the parent tag. Its sort of a reverse relationship to the CFAssociate tag, but one that I have found to be more useful.

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel