Breaking Out Of A ColdFusion Custom Tag Loop

Posted September 23, 2008 at 9:00 AM by Ben Nadel

Tags: ColdFusion

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.



Reader Comments

Sep 23, 2008 at 10:44 AM // reply »
304 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.


Sep 23, 2008 at 10:56 AM // reply »
10,640 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.


Sep 23, 2008 at 12:09 PM // reply »
304 Comments

Ohhhhh ok. That makes sense.


Sep 23, 2008 at 12:45 PM // reply »
10,640 Comments

@Ray,

I'll probably never use something like this, but I figure it's good to have in the bag.


Sep 23, 2008 at 12:46 PM // reply »
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


Sep 23, 2008 at 1:23 PM // reply »
10,640 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.


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
InVision App - Prototyping Made Beautiful With Prototyping Tools Ben Nadel's Company - Epicenter Consulting Recent Blog Comments
Feb 10, 2012 at 7:21 PM
jQuery AJAX Strips Script Tags And Inserts Them After Parent-Most Elements
Update! Instead of $(eval(options.insertAfter)).after(data['insertData']); I now use: var ajaxNode = document.createElement('span'); var parent = $(eval(options.insertAfter))[0].parentNode; ... read »
Feb 10, 2012 at 6:18 PM
jQuery AJAX Strips Script Tags And Inserts Them After Parent-Most Elements
encountered this same, what I consider, jQuery bug last week. I'm building a site in which I load some content via AJAX. This content contains Linkedin share button placeholders which Linkedin API ne ... read »
Feb 10, 2012 at 11:30 AM
Cross-Origin Resource Sharing (CORS) AJAX Requests Between jQuery And Node.js
After you understand the concepts here, this is an awesome cheatsheet for enabling CORS in just about anything http://enable-cors.org/ ... read »
JM
Feb 10, 2012 at 9:10 AM
My Safari Browser SQLite Database Hello World Example
@Amy, Here is a very good tutorial on how to use JOIN: http://www.sqltutorial.org/sqljoin-innerjoin.aspx ... read »
Feb 10, 2012 at 4:42 AM
Building A Twitter-Inspired RESTful API Architecture In ColdFusion
This is great, very useful Ben. I spotted a small typo in the api.cgm listing: <cfthrow type="Unauthroized" /> Cheers Stefan ... read »
Feb 9, 2012 at 10:35 PM
CFDirectory Filtering Uses Pipe Character For Multiple Filters (Thanks Steve Withington)
I was wondering if there would be a filter you could apply so that you got everything but what you included in the filter. As in show me all docs that are not a .pdf. ... read »
Feb 9, 2012 at 10:29 PM
Learning ColdFusion 9: Application-Specific Data Sources
@Ben, No offence, but if people were really wanting advanced features they would be using a platform like ASP.NET MVC. CFML is so structurally compromised as a tag-based scripting language that ... read »
Feb 9, 2012 at 10:03 PM
Subversion - Cleanup Failed To Process The Following Paths
@Leviaguirre, do you still have problems with this? ... read »