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:
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:
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.
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.
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.
Ohhhhh ok. That makes sense.
I'll probably never use something like this, but I figure it's good to have in the bag.
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>
<!--- loop.cfm; based on your one--->
<cfif structKeyExists(thistag, "breakAttributes") and thistag.breakAttributes.break>
<cfset thistag.generatedContent = "">
<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>
<cfset variables.index = variables.index + 1>
<cfif variables.index lte attributes.iterations>
<cfset caller[attributes.index] = variables.index>
<!--- 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).
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.