Randomly Executing Only One ColdFusion Custom Tag Child
Last night, I gave a presentation on Advanced ColdFusion Custom Tags to the New York City ColdFusion User Group. I was hoping to have some time at the end to build a ColdFusion custom tag demonstration together, but unfortunately, we ran short on time. The demonstration, however, does cover a very interesting custom tag work flow, so I thought I would post here.
The problem that I wanted to solve with nested custom tags was randomly executing one of the child tags. Generally, when I want to randomly execute a chunk of ColdFusion code, I put it into a CFSwitch statement:
<cfswitch expression="#RandRange( 1, 4 )#">
<cfcase value="1">
(1) Hey there, how are you?
</cfcase>
<cfcase value="2">
(2) It's so nice to see you!
</cfcase>
<cfcase value="3">
(3) I'm sorry, I can't recall your name?
</cfcase>
<cfcase value="4">
(4) How's your mother doing?
</cfcase>
</cfswitch>
As you can see, this randomly selects a number and then executes the case statement with the given value. If you've never done this before, this might seems like a perfect solution. But, anyone who's ever had to maintain something like over time knows that this seemingly simple tag setup can be extremely frustrating for a number of reasons. First off, every time you add a case, you have to remember to update the RandRange() statement. Second, every time you add a case, you have to make sure that the case value is unique. And third, which is the worst case scenario, if you want to remove a case, you have to shift all of the sibling case values so that there are no holes in the possible value set produced by the RandRange() function.
To better solve this problem, ideally what we want to create is a switch-like statement that doesn't have an expression or any case values; you simply provide the case statements and the switch statement will randomly execute one of them. This way, adding and removing cases becomes painless.
Often times, when building ColdFusion custom tags, I will write out the calling code first so that I can get a sense of how I want custom tags to function. In a way, this is like using Interface Driven Architecture to design your custom tag API:
<!--- Import the random switch tags. --->
<cfimport prefix="random" taglib="./" />
<!--- Select one of the following cases randomly. --->
<random:switch>
<random:case>
(1) Hey there, how are you?
</random:case>
<random:case>
(2) It's so nice to see you!
</random:case>
<random:case>
(3) I'm sorry, I can't recall your name?
</random:case>
<random:case>
(4) How's your mother doing?
</random:case>
</random:switch>
As you can see, there is no switch expression and there are no case values; I merely create the switch parent and then define as many child cases as I want.
At first, this seems like a really simple problem - just randomly execute one of the child tags. But, due to the execution work flow of ColdFusion custom tags, this is more complicated than it appears. See, when a parent custom tag starts executing, it doesn't know anything about its child tags. The parent tag can only know about its child tags once they have already executed. But in our case, that's too late - if the child tags have already executed, then we can't randomly execute only one of them.
So, how do we solve this problem? We can't do it in one pass for the reasons mentioned above. What we have to do is pass over the child tags twice - once to get the child tag count and once to execute the randomly selected child (while bypassing all others).
To get this kind of behavior, the parent tag and the child tags have to really work nicely together. And more than that, the child tag has to listen to and comply with the parent tag. The child tag doesn't have a lot of behavior, so I figured I would write it first (again, using an Interface Driven Architecture for my API). Here's what I came up with:
Case.cfm
<!--- Get the parent tag reference. --->
<cfset VARIABLES.SwitchTag = GetBaseTagData( "cf_switch" ) />
<!--- Check to see if child should execute. --->
<cfif NOT VARIABLES.SwitchTag.ChildShouldExecute()>
<!---
The switch tag said not to execute, so exit out of
this tag before any code in the body can execute.
--->
<cfexit method="exittag" />
</cfif>
The child tag (case) is really simple. All it needs to do is get a reference to its parent tag (switch) and ask the parent tag if it should be executing. If not, the child tag simply exits out, preventing any of its body to execute. In using this architecture, the child tag is completely hidden from any work flow concerns. All it ever needs to know is whether or not it should execute and who to ask to get that information.
The parent tag, on the other hand, is still short but significantly more complicated. It has to work over two different passes: Collection, in which it counts the children but doesn't let any of them execute, and Execution, in which it only allows the randomly selected child to execute.
To get from one phase to the other (Collection to Execution), we use the CFExit tag with the method Loop. This allows the end mode of the switch tag to jump back up to the tag body and re-execute all of the child tags. We then use a user defined function (UDF) within the parent tag, ChildShouldExecute(), to count the children on the first pass and then to allow only the targeted child to execute on the second pass.
Switch.cfm
<cffunction
name="ChildShouldExecute"
access="public"
returntype="boolean"
output="false"
hint="I expect to be called by a child tag and I return a boolean as to whether the given tag should execute.">
<!--- Define the local scope. --->
<cfset var LOCAL = {} />
<!--- Get the switch tag context. --->
<cfset var CONTEXT = GetBaseTagData( "cf_switch" ) />
<!---
Check to see which mode we are in. If we are collection,
then we only want the count and we don't want any child
tags to execute.
--->
<cfif (CONTEXT.SwitchMode EQ "Collection")>
<!--- Collection mode. --->
<!--- Increment the child tag count. --->
<cfset CONTEXT.ChildCount++ />
<!---
Return false since we don't want any child tags to
execute at this point.
--->
<cfreturn false />
<cfelse>
<!--- Execution mode. --->
<!---
Now that we're in the execution mode, we have to keep
track of the number of tags that call this function.
We want to return false unless the given tag is at the
target index.
--->
<!--- Increment child index. --->
<cfset CONTEXT.ChildIndex++ />
<!---
Check to see if the current index matches the
target index.
--->
<cfif (CONTEXT.ChildIndex EQ CONTEXT.TargetIndex)>
<!--- This is the correct tag. Let it execute. --->
<cfreturn true />
<cfelse>
<!--- This is not the target tag. Do not execute. --->
<cfreturn false />
</cfif>
</cfif>
</cffunction>
<!--- ----------------------------------------------------- --->
<!--- ----------------------------------------------------- --->
<!--- Check to see which mode we are executing. --->
<cfswitch expression="#THISTAG.ExecutionMode#">
<cfcase value="Start">
<!---
In the start mode, we don't yet know how many
children we have. Therefore, we have to do one pass
over the child tags to gether the count before we
actually execute any of them.
--->
<cfset VARIABLES.SwitchMode = "Collection" />
<!--- Keep a counter of the number of tags. --->
<cfset VARIABLES.ChildCount = 0 />
<!---
Keep an index of the child tag that is running. This
will only come into play on the second pass when we
know which target index we want to execute.
--->
<cfset VARIABLES.ChildIndex = 0 />
<!---
Keep a variable for the target child tag to execute.
This will only be relevant on the second pass once
we've counted the child tags.
--->
<cfset VARIABLES.TargetIndex = 0 />
</cfcase>
<!--- ------------------------------------------------- --->
<cfcase value="End">
<!---
At this point, we have either finished collecting the
child tags OR we have actually executed on. We only
need to take action if the current mode is Collection;
otherwise, just let the tag exit.
--->
<cfif (VARIABLES.SwitchMode EQ "Collection")>
<!--- Select a random child based on the count. --->
<cfset VARIABLES.TargetIndex = RandRange(
1,
VARIABLES.ChildCount
) />
<!---
Change the mode to be execution rather than
collection. This will signal to the child tags
that its time to execute.
--->
<cfset VARIABLES.SwitchMode = "Execution" />
<!---
Loop back to body to allow one of the child tags
a chance to execute.
--->
<cfexit method="loop" />
</cfif>
<!---
ASSERT: If we've gotten here, we are in execution
mode and there's nothing left to do. Just let the
tag exit naturally.
--->
</cfcase>
</cfswitch>
As you can see in the ChildShouldExecute() method, when the parent tag's mode is, "Collection," the method always returns False as none of the child tags should execute. On the second pass (Execution mode), the method return true only when the invoking child's index is equal to that of the randomly selected target index.
Now, when I run the demo code a few times, I get the following output:
(2) It's so nice to see you!
(3) I'm sorry, I can't recall your name?
(1) Hey there, how are you?
(4) How's your mother doing?
(1) Hey there, how are you?
(2) It's so nice to see you!
(2) It's so nice to see you!
(3) I'm sorry, I can't recall your name?
As you can see, each one of the child case tags is randomly executed with each page request. And, it does so without having any expression or case values to evaluate. To me, not only is this the right solution for the problem, it's also a really exciting exploration of the ColdFusion custom tag work flow.
Want to use code from this post? Check out the license.
Reader Comments
Interesting! One small comment. Not saying this is better - but - you may want to consider using cfassociate as a way for the child to 'inform' the parent of its existence. The parent can then check the arrayLen. It's probably the same amount of code. I just recommend it since it is the 'standard' (IMO) way for a child to share information with the parent. Of course, in this case you just want a _count_, not shared data.
@Ray,
The problem with CFAssociate in this case is that is merely passes information back to the parent - it doesn't have any impact on execution.
Because I am passing over the child tags twice, I need to make sure that they don't execute on the first pass at all; even on the second pass, I need to make sure that only the selected child tag executes. This requires more communication than the CFAssociate provides.
I think you misread me - or I wasn't clear. I didn't mean to ONLY use cfassociate, but to use that instead of the counter variable.
@Ray,
Ah, gotcha. Sorry about that.
No problem. Nice to see you give some love to Custom Tags. Poor things got forgotten in all the CFC buzz. ;)
Here's a challenge. Given a parent tag, and N child tags, make it so that the child tags execute backwards. (Or their output/operations are done backwards.)
Totally useless.... but can you do it? Hmmm? Can you?!?! ;)
Or - and this may actually not be 100% useless. Instead of executing a random child (boy, that sounds bad), execute them all, but in random order.
@Ray,
Agreed! Plus, I don't think there is a way to accomplish this example with CFCs ;)
@Ray,
Oooh, sounds like a good lunch-time challenge!
<cfcomponent output="false">
<cffunction name="execute">
<cfset var local = StructNew() />
<cfset local.options = StructKeyList(this) />
<cfset local.options = ListToArray( ListDeleteAt( local.options, ListFindNoCase(local.options, "execute"))) />
<cfset local.random = RandRange(1, ArrayLen(local.options)) />
<cfinvoke component="#this#" method="#local.options[local.random]#" />
<cfreturn />
</cffunction>
<cffunction name="message1">
Hi 1!
</cffunction>
<cffunction name="message2">
Hi 2!
</cffunction>
<cffunction name="message3">
Hi 3!
</cffunction>
<cffunction name="message4">
Hi 4!
</cffunction>
<cffunction name="message5">
Hi 5!
</cffunction>
</cfcomponent>
And put this in your test file:
<cfset CreateObject("component", "Random").execute() />
Should have tried to format that better :)
Jon, while that works, I think you don't get the point here in regards to _how_ it is written. Considering both examples, I can easily add a new random item by just writing one more child tag. In yours, I have to go into the CFC.
I mean it may give you the same result, but the API is significantly simpler in the custom tag version. (imho)
Custom tags rock :) I think complex nested custom tags are one of the most under used features of CF. The cfassociate tag, GetBaseTagData() and GetBaseTagList() can do some pretty awesome stuff and until I wrote ColdExt I had no idea just how powerful a few custom tags could be.
The cfexit/loop tag is the one thing I haven't used yet because I haven't had a use case for re-executing child tags but I think this is a good reason for it as you've shown - conditional execution of a tag only after the number of child tags or some other meta data about them are known.
@Jon,
I like the concept, but with what Ray said, the custom tags make the interface really easy to update.
@Justin,
Agreed - custom tags are definitely under utilized. I'm hoping to make a video of my custom tags presentation to post online.
@Ray: I was mostly just trying to show that you could do this with a CFC, since Ben didn't think it could be done. I'm not really sure that the tag based version is any more maintainable though: in either case, you are going to have to open a file and add some text wrapped in a CF tag. With the CFC version, the logic and the actual messages can be reused in multiple locations, and the CFC can be extended, so you could remove all of the messages from Random, and create RandomEmail.cfc that extends Random but adds in random messages for Email, and RandomComment.cfc that does the same, but for blog comments. With the base case, I think a CFC is a superior solution, but I'd be hard pressed to figure out how to handle the other case you suggested (executing each of them in reverse order) without passing in some kind of guide as to which comes first.
@Jon,
While I think your CFC concept is really nice, and the idea of extending a CFC is inviting; but, I think the tag-based version has several advantages:
1. You don't have to create a CFC for each use case. This would be like having to create a CFC instance for every time you wanted to use the CFLoop tag - yes, you could if the language required it, but how nice is it that we don't have to do that.
2. You can dynamically generate case statements at run time. Example:
<cfif SESSION.USER.IsAdmin>
. . . . <random:case>
. . . . . . . . -some admin-specific message-
. . . . </random:case>
</cfif>
Kind of a weak example, but I think you get the point.
3. Because the custom tags execute in the context of the calling page, the code within the custom tags has access to the page's variable scope. If you encapsulate this functionality inside of a CFC, you cannot access the page-scoped variables without having to pass them in. Not impossible, but again, just a slight difference that makes the tag-based version a bit more elegant / flexible in my opinion.
@Ben, Yeah, I'd considered the scoping example when I was putting mine together, and that is one of the advantages the tag based implementation does have over CFCs. I keep thinking though that from a maintenance standpoint, if I needed to have the same set of random possibilities in more than one place, the CFC method would prove better in the long run, since you have a single point to edit.
All this is hypothetical to me though... I've never had to execute random code like this. Is this something you do often?
@Jon,
No, certainly not something that I have to do a lot. The only times I really do use this sort of thing is when randomizing Advertisement output or any other "non-crucial" display widgets on a random order.
I don't have CMS for things like that, so it's generally choosing from static code blocks. If you were pulling from a DB, that's a totally different world and different solution :)
I updated the tags to get 1..N random selections to show. You can also use the keyword "all" to get them all to show in random order:
www.bennadel.com/index.cfm?dax=blog:1571.view
@Ray,
I only didn't add a "reverse" keyword because I figured that wouldn't be random. But it could easily be added.
Hi,
How would you use custom tags to dynamically generate form controls that may appear in one or many cflayoutarea tabs?
My admin users of the admin site that I am developing need form prototyping capabilities. For each form control they create, a Web page with a form is available to them with which they may assign:
1. an HTML name attribute value for the form control
2. a form control type (radio button, text box, list, dropdown menu, etc.)
3. a label for the form control
4. option values or autosuggest values for the form control
5. an integer that signifies the order in which the option values should appear within a SELECT control
6. an integer that signifies the order in which the form control should appear within the cflayoutarea tab
7. a name that signifies the cflayoutarea tab on which the form control should appear
8. a boolean to flag the control as inactive if the user wishes the control to be ignored when dynamically generating the form
The above values are stored in the site's database for each form control.
I am using CFSELECT and CFINPUT for their Ajax binding and I want a custom tag that automatically generates CF form controls within a four column table, with two columns for the controls' labels and two columns for the controls themselves.
Thanks!
Mike
@Mike,
I am not sure what you are asking? You can certainly use custom tags to build out form fields, but I am not sure what the question is exactly?