I Wish: ColdFusion Custom Tag Query Looping
Posted November 3, 2008 at 8:54 PM by Ben Nadel
I'm not sure why I want this to work, but there's something about it that would just be neat. I think if this could be done, it would open up potentially interesting ways to loop over a ColdFusion query in custom ways that still feel very natural. I was trying to do something like this:
- <cfswitch expression="#THISTAG.ExecutionMode#">
- <cfcase value="Start">
- <!--- Param the query name. --->
- <!--- Default the loop index. --->
- <cfset VARIABLES.LoopIndex = 1 />
- <cfcase value="End">
- <!--- Check to see if loop index is not too high. --->
- <cfif (VARIABLES.LoopIndex LT CALLER[ ATTRIBUTES.Query ].RecordCount)>
- <!--- Increment the index. --->
- <cfset VARIABLES.LoopIndex++ />
- <!--- Loop over *only* the selected row. --->
- <!--- Run the body of the tag again. --->
- <cfexit method="loop" />
Handling the first row would obviously be difficult - I think it would have to be row 1. But, in the end of the tag, I'm trying to re-execute the ColdFusion custom tag body while in the context of the current query iteration (such that the calling page would think that it was actually in the #CurrentRow# of the query). This doesn't actually work as the moment you exit out of the custom tag, you lose the query context.
Anyway, not sure if anyone ever tried to do this or found a way to do it. Just having some fun on a Monday night, trying to think outside of the box.
Yeah, that's an interesting thought...
I would think you could do that, but you would have to be willing to be a little sloppy with the encapsulation of the custom tag. Which some people are okay with and others aren't. But if you were to create say a request variable to hold the current index, then you could increment it on each pass through the tag. The problem you'd run into is if you wanted to call the tag more than once. You could clear the request variable after the closing tag, but you still wouldn't be able to nest them.
I would pass the query into the tag as an object though, instead of passing in the name of the query and referencing the caller scope. I gather it would retain the query through each pass.
I don't think having a request-based value would help. You'd still have the problem of having the query be in the appropriate loop index from the CALLER standpoint. Really, the ultimate goal would be to supply an arbitrary "order index" and have the calling page not have to worry about it or use notation like:
query[ column ][ row ]
... but rather:
... no matter what iteration we are on.
Actually come to think of it ColdFusion 9 will add the feature that will make the equivalent of this available... the cfcontinue tag -- then you can just cfloop with the query and cfcontinue instead of dealing with the custom tag
Or not... since the cfexit is in the end tag...
okay, backing up a bit, I'm not sure what the custom tag adds over a cfloop
The custom tag adds the ability to order the query in a totally arbitrary way. I am not saying that I have any great use cases right now, but if the ability was there, perhaps I could come up with one. Image a loop tag that could loop over the query backwards without having to use explicit row indexing?
oh okay... like a step beyond what you can do with grouping.
Yeah, exactly. Heck you could even make a version that loops over a query in a totally random order (just for yucks).
What does that add that you can't get by changing the SQL statement?
I think the idea here is not so much that the query could be changed to reflect the infinite type of custom looping - the idea here is that the query doesn't need to know about it. I know that might seem silly - why have a query that uses different sorting than the output, but if you think about all the logic that goes into it, I think it makes sense to abstract that out into its own black box.
Imagine a totally crazy scenario where one user can only display odd rows and one user can only display even rows. Is this the kind of logic that you want to building into your SQL statement (odd vs. even row logic)? Or is this something that you want to factor out into something else.
Remember, all of the logic in the custom tag *could* be moved into the calling page; but again, the idea here is we might want to make certain logic easily reusable.
Okay, that makes sense. And along the same lines, the idea behind a SQL result set is that it's essentially an unordered set - ordering in a SQL statement is only done for convenience in a list. I'll have to think on this a bit.
Yeah, I think it's kind of difficult for folks (self included) to "click" with this idea because we can't think of any really practical use-cases that would serve as examples of why you might want this.
Seems like the only way to really achieve this is (at least currently) is to separate the content into an include file or something instead of the end-tag syntax. Although the end-tag syntax would be sort of ideal for this kind of thing because it would make it easier to use. XSLT also offers some options, but again that's moving to greater complexity.
I agree, not that many use cases. The one that I actually had in mind when doing this was, or rather, how I ended up here, was displaying a datagrid that was listed alphabetically DOWN rather than Across:
[ A ][ C ][ D ]
[ A ][ D ][ E ]
[ B ][ D ][ E ]
[ B ][ D ][ E ]
Ideally, when outputting a query, you just want to do something like:
. . . . #qData.value#
If I had a query that came back alphabetically, I thought I could come up with a custom-tag way to display it using the above code even though the across-output would *not* be alphabetical.
Anyway, when I got to thinking like that, I thought it would be nice to have a custom tag that would allow me to somehow alter the order of query output in a more natural way.
Oh yeah, three-columns down in a grid is always a fidgety and annoying bit of code to write... You end up doing like end-row ceiling(recordcount/3) and then mathematically figure out the index of the values in columns 2 & 3 as you loop down...
<cfset rows = ceiling(qry.recordcount / 3) />
<cfoutput query="qry" startrow="1" maxrows="#rows#">
<cfloop index="add" list="0,#rows#,#rows*2#">
<cfset x = currentrow + add />
Who wouldn't want an easier way of doing that?!
I agree - everyone would want an easier way :)