Randomly Executing 1..N Nested ColdFusion Custom Tags

Posted April 23, 2009 at 8:17 AM by Ben Nadel

Tags: ColdFusion

After my post yesterday on the exciting work flow involved in randomly executing a nested ColdFusion custom tag, Ray Camden had some comments that inspired me to update the way the tag works. Rather than just randomly executing a single tag, I wanted to provide a way for the user to specify how many child tags to randomly execute. By default, it will execute just one; but, you can now specify any number of child tags or simply use the keyword, "all," to get the entire list to output in a random order:

  • <!--- Import the random switch tags. --->
  • <cfimport prefix="random" taglib="./" />
  •  
  •  
  • <!--- Select one of the following cases randomly. --->
  • <random:switch randomize="2">
  •  
  • <random:case>
  • (1) Hey there, how are you?<br />
  • </random:case>
  •  
  • <random:case>
  • (2) It's so nice to see you!<br />
  • </random:case>
  •  
  • <random:case>
  • (3) I'm sorry, I can't recall your name?<br />
  • </random:case>
  •  
  • <random:case>
  • (4) How's your mother doing?<br />
  • </random:case>
  •  
  • </random:switch>

Notice that the Switch.cfm tag now has a Randomize attribute. If you exclude this attribute, it defaults to 1 (one). You can specify a number, as I am doing in the demo, or you can pass in, "all." When we run the code above, we get our two randomly output values:

(4) How's your mother doing?
(3) I'm sorry, I can't recall your name?

Because we are executing more than one child tag, we need to update the way the tag works; rather than iterating once for collection and once for execution, the Switch.cfm custom tag now needs to iterate once for collection and as many times as is needed to execute the child tags. The number of post-collection iterations is not necessarily equal to the number of tags to execute. If two consecutive randomly chosen indexes are "in order", they will be executed in the same iteration. And so, the worst case scenario (completely backwards) is N iterations whereas the best case scenario (in original order) is one post-collection iteration.

The child tag, Case.cfm, has remained mostly unchanged. The only update I had to do was check the ExecutionMode of the tag before checking with the parent tag. This is something I technically should have done in yesterday's post; but, since there was no mutation of information, it didn't matter. Now, we are actually updating an internal collection with each call, so it becomes important to only perform the check in the proper mode:

Case.cfm

  • <!--- Only check execution in start mode. --->
  • <cfif (THISTAG.ExecutionMode EQ "Start")>
  •  
  • <!--- 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>
  •  
  • </cfif>

The main tag, Switch.cfm, on the other hand did have to be significantly updated. Rather than just using a single target index, since we might execute 1..N children, I have to build up a collection of child indexes and then randomly shuffle them. Then, rather than a single post-collection iteration, I have to keep iterating until I no longer have indexes left in my collection (I delete each index off the top of the collection as the given child tag executes).

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. --->
  •  
  • <!--- Add the new index to the index collection. --->
  • <cfset ArrayAppend(
  • CONTEXT.ChildIndexCollection,
  • (ArrayLen( CONTEXT.ChildIndexCollection ) + 1)
  • ) />
  •  
  • <!---
  • 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
  • FRONT of the index collection.
  • --->
  •  
  • <!--- Increment child index. --->
  • <cfset CONTEXT.ChildIndex++ />
  •  
  • <!---
  • Check to see if the current index matches the
  • target index.
  • --->
  • <cfif (
  • ArrayLen( CONTEXT.ChildIndexCollection ) AND
  • (CONTEXT.ChildIndex EQ CONTEXT.ChildIndexCollection[ 1 ])
  • )>
  •  
  • <!--- Delete the first index. --->
  • <cfset ArrayDeleteAt(
  • CONTEXT.ChildIndexCollection,
  • 1
  • ) />
  •  
  • <!--- 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">
  •  
  • <!--- Param tag attributes. --->
  •  
  • <cfparam
  • name="ATTRIBUTES.Randomize"
  • type="string"
  • default="1"
  • />
  •  
  • <!---
  • The randomize attribute determines how the child tags
  • get randomized. There are several possibilities
  • including keywords and numbers:
  •  
  • 1 .. N: Outputs the given number of random children.
  •  
  • All: Outputs all in random order.
  • --->
  •  
  •  
  • <!---
  • Check to make sure that the ranomize attribute has a
  • valid value.
  • --->
  • <cfif NOT (
  • (ATTRIBUTES.Randomize EQ "All") OR
  • (
  • IsNumeric( ATTRIBUTES.Randomize ) AND
  • (Fix( ATTRIBUTES.Randomize ) EQ ATTRIBUTES.Randomize) AND
  • (ATTRIBUTES.Randomize GT 0)
  • ))>
  •  
  • <!--- Bad param value. --->
  • <cfthrow
  • type="InvalidAttributeValue"
  • message="Randomize must be an integer greater than zero or ALL."
  • />
  •  
  • </cfif>
  •  
  •  
  • <!---
  • 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 an array that will contain each index of the
  • child tag. At first, this will be bulit up, then it
  • will be randomized and leveraged.
  • --->
  • <cfset VARIABLES.ChildIndexCollection = [] />
  •  
  • <!---
  • Keep an index of the child tag that is running. This
  • will only come into play on the secondary passes when
  • we know which target index(es) we want to execute.
  • --->
  • <cfset VARIABLES.ChildIndex = 0 />
  •  
  • </cfcase>
  •  
  •  
  • <!--- ------------------------------------------------- --->
  •  
  •  
  • <cfcase value="End">
  •  
  • <!---
  • Check to see which mode we are in. If we are
  • collecting information or acting on it. If we are
  • collecting, it means that we have to randomize our
  • index collection. If not, we have to loop back until
  • we have no more indexes to execute.
  • --->
  • <cfif (VARIABLES.SwitchMode EQ "Collection")>
  •  
  • <!--- Randomize the index collection. --->
  • <cfset CreateObject( "java", "java.util.Collections" ).Shuffle(
  • VARIABLES.ChildIndexCollection
  • ) />
  •  
  • <!---
  • Now that we have our child index collection
  • randomized, we have to see how many children we
  • need to execute. If we have all, keep all. If we
  • have a number, loop backwards, deleting indexes,
  • until we have the right count.
  • --->
  • <cfif (ATTRIBUTES.Randomize EQ 1)>
  •  
  • <!---
  • Since this it the most common case, optimize
  • for it by just overwriting the entire array.
  • --->
  • <cfset VARIABLES.ChildIndexCollection = [
  • VARIABLES.ChildIndexCollection[ 1 ]
  • ] />
  •  
  • <cfelseif (ATTRIBUTES.Randomize NEQ "All")>
  •  
  • <!---
  • We are selecting 2..(N-1) values, so just
  • start looping backwards deleting them.
  • --->
  • <cfloop condition="(ArrayLen( VARIABLES.ChildIndexCollection ) GT ATTRIBUTES.Randomize)">
  •  
  • <!--- Delete last index. --->
  • <cfset ArrayDeleteAt(
  • VARIABLES.ChildIndexCollection,
  • ArrayLen( VARIABLES.ChildIndexCollection )
  • ) />
  •  
  • </cfloop>
  •  
  • </cfif>
  •  
  • <!---
  • 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" />
  •  
  • <cfelse>
  •  
  • <!---
  • We just finished a mode of execution. Let's see if
  • we have any more child tag indexes to execute. If
  • so, loop back.
  • --->
  • <cfif ArrayLen( VARIABLES.ChildIndexCollection )>
  •  
  • <!--- Reset the child index. --->
  • <cfset VARIABLES.ChildIndex = 0 />
  •  
  • <!--- Run through tags again to execute next. --->
  • <cfexit method="loop" />
  •  
  • </cfif>
  •  
  • </cfif>
  •  
  • </cfcase>
  •  
  • </cfswitch>

Because one custom tag execution is the default, and the most likely case, I optimize for that after the index collection has been shuffled. But, if the user wants to execute 2 or more children, I have to loop backwards over the array, deleting indexes that are not relevant. Then, I simply keep looping until no more indexes are left in the collection.

I am not sure how useful a tag like this is, at least not with the more-than-one option. I think the real usefulness of this kind of exploration is just in seeing what kind of work flows can exist within ColdFusion custom tags. I think that custom tags are something that are under utilized, so hopefully seeing this kind of stuff might spark some excitement and some good conversation.




Reader Comments

There are no comments posted for this web log entry.

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 12, 2012 at 3:37 AM
Learning ColdFusion 8: CFImage Part III - Watermarks And Transparency
Hi Ben, Just to ask currently it is placed bottom right corner, if i need to replace the same rendered image on the bottom left side or in the bottom center, how that can be calculated. bottom ce ... read »
Feb 11, 2012 at 9:29 PM
Use jQuery's SlideDown() With Fixed-Width Elements To Prevent Jumping
I can't say how glad I am that I found your post. Thank you very much. ... read »
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 »