Ask Ben: Executing ColdFusion Custom Tag Code If First Run Only
Someone asked me earlier about executing part of a ColdFusion custom tag only if it was the first instance of the tag run of the given page. Basically, what they were trying to do was create reusable UI widgets that had associated Javascript. The given UI widget tags could be used any number of times on the page, but they only wanted the Javascript portion to render once, in the first custom tag execution as it would initialize each UI widget on page load.
To do this, each instance of the custom tag would have to be, to some extent, aware of each of its siblings. Since each tag executes inside of its own bubble, I have to keep ColdFusion custom tag meta data in one of the page scopes to and from which each custom tag can write and read respectively. Traditionally, I use the REQUEST scope as this is both globally available and persistent for a single page request. To avoid naming collisions, I like to create a name space within the REQUEST scope that is specific to custom tags in general and to this tag specifically.
By paraming this ColdFusion custom tag "name space" in the tag ATTRIBUTES scope, it allows each tag to have a default name space with the ability to be overridden by the calling context. Because this name space is dynamic, you would think it would be complicated to work with; but, due to the behavior of the CALLER scope and the auto-struct-path creation behavior of ColdFusion, it's actually quite easy!
To demonstrate this, I have some test code below that executes a demo ColdFusion custom tag three times. Each of the custom tags has its own HTML output plus some Javascript output. The Javascript output, as per the readers question, will only be output once during the first execution of the custom tag:
<!--- Run the custom tag. --->
<cf_demo />
<!--- Run the custom tag. --->
<cf_demo />
<!--- Run the custom tag. --->
<cf_demo />
When we run this code, we get the following PAGE SOURCE output (I've stripped some white space for readability):
<script type="text/javascript">
// Script would go here.
</script>Tag 1<br />
Tag 2<br />
Tag 3<br />
As you can see, the SCRIPT tag has only been rendered once during the first custom tag execution, but the HTML output has been rendered once for each tag.
So, how does this work? Here is the ColdFusion custom tag that was executed:
Demo.cfm
<!--- Turn on explicit output. --->
<cfsetting enablecfoutputonly="true" />
<!---
Param the tag attribute for our name space. To make sure
that the meta data that we store with this tag does not
conflict with other page variables, we will create namespace
for storage. This name space can be overridden by the
calling code.
--->
<cfparam
name="ATTRIBUTES.NameSpace"
type="variablename"
default="REQUEST.CustomTagNameSpaces.Demo"
/>
<!---
Check to see if the namespace for this tag exists yet.
If not, we will create a default version.
--->
<cfif NOT StructKeyExists( CALLER, ATTRIBUTES.NameSpace )>
<!--- Create default name space for this tag. --->
<cfset CALLER[ ATTRIBUTES.NameSpace ] = {
JavaScriptRendered = false,
TagCount = 0
} />
</cfif>
<!---
ASSERT: At this point, we can access the name space for
this tag (even if it was just created).
--->
<!--- Check to see if the Javascript has been rendered. --->
<cfif NOT CALLER[ ATTRIBUTES.NameSpace ].JavaScriptRendered>
<!--- Render Javascript. --->
<cfoutput>
<script type="text/javascript">
// Script would go here.
</script>
</cfoutput>
<!--- Flag script as being rendered. --->
<cfset CALLER[ ATTRIBUTES.NameSpace ].JavaScriptRendered = true />
</cfif>
<!---
ASSERT: At this point, we know that the Javascript has
either been rendered by this tag or a previous tag.
--->
<!--- Render tag content. --->
<cfoutput>
Tag #++CALLER[ ATTRIBUTES.NameSpace ].TagCount#<br />
</cfoutput>
<!--- Turn off explicit output. --->
<cfsetting enablecfoutputonly="false" />
<!--- Exit tag. --->
<cfexit method="exittag" />
As you can see, I am paraming a name space value in the custom tag ATTRIBUTES scope:
REQUEST.CustomTagNameSpaces.Demo
This value has to be a valid "variable name" data type otherwise we won't be able to leverage the awesome power of the ColdFusion CALLER scope. Like I said before, I like to use a REQUEST-based struct; but, because this variable name is defaulted in the ATTRIBUTES scope, the calling context could easily override it:
<cf_demo name space="VARIABLES.TagData" />
Notice that once we have the name space variable, I make no explicit references to the path, only to the variable which contains it. With this variable, we can check to see if the name space exists using StructKeyExists():
<cfif NOT StructKeyExists( CALLER, ATTRIBUTES.NameSpace )>
Because the CALLER scope does not act like a standard ColdFusion struct, calling StructKeyExists() checks the dynamic variable path, not the key value (NOTE: IsDefined() is almost never needed in ColdFusion!!). Also in this vein, by using the NameSpace variable as a key in the CALLER scope, we dynamically create the full variable path, not just the given key:
<cfset CALLER[ ATTRIBUTES.NameSpace ] = { ... } />
I find that this approach gives us the most usability but provides great flexibility while at the same time, avoiding any naming conflicts that might arise. Every time I use the CALLER scope, I simply love the way that ColdFusion has chosen to implement it - it just makes life so darned elegant.
Want to use code from this post? Check out the license.
Reader Comments
I do a similar thing in ColdExt to keep track of loaded JS resources, though I just use the request scope directly, as you say you would traditionally do. Is the combination of the caller scope + StructKeyExists() letting you do something that wouldn't normally be possible, or is it just letting you reference your variables in a shorter (but perhaps more confusing) manner? I think you could still use the request scope directly and have just as much flexibility, e.g:
<cfset request._my_taglib.[attributes.namespace]._this_tag_name.jsRendered = true>
Maybe I'm missing something? :) Or is the point that you can use an arbitrary scope other than the request scope?
I had a couple of reactions to this scenario and wanted to share.. They're not a knock on your nifty solution though.
1. The single-execution logic could be placed in the JavaScript side; although using a global JS var to accomplish this, it might feel better than having a custom tag sticking its grimy fingers where they don't belong.. ;-D
<pre>var executed=false;
function doStuff()
{
if(!executed) {
...do your stuff...
executed=true;
}
}</pre>
Then your custom tag just writes the JS every time and doesn't worry about that logic.
2. A bit more "controversial"... But a CFC could handle this task pretty well, and could maintain the state between each execution. This using CFCs for display is obviously blasphemous from the perspective of CFCs in the "model", but the flexibility of CFCs can come in handy here for a very specific "view" purpose.
Don't get me wrong though. I'm not a custom tag hater.
I dunno. What do you think?
Oops, the pre tags aren't rendering, but you get the point.
@Justin,
The idea behind the CALLER scope is that you don't have to use the REQUEST scope at all. The CALLER scope allows you to reach into the calling context and use whatever variable string was passed in (or defaulted) in the attributes. As such, you could override with a VARIABLES-scopes key as I demonstrate part way in the blog.
Think about something like CFThread; perhaps you are using CFThread to burn some HTML to flat files. You probably wouldn't want to use the REQUEST scope in that case because the context of the custom tag (cfthread) might be running in parallel with other threads all doing the same thing. In such a case, you would want them to use their own name-space such as (THREAD.CustomTagData).
Without using the CALLER scope, also, its much harder to reference an unknown variable location. You can set it using dynamic variable naming:
<cfset "#ATTRIBUTES.NameSpace#" = StructNew() />
... of course, if this is a VARIABLES-scoped value, it will incorrectly store it in the custom tag's VARIABLES scope.
But, accessing it is much less elegant; I believe you would have to use an Evaluate() call:
<cfset objNameSpace = Evaluate( ATTRIBUTES.NameSpace ) />
Wicked gross, right?!?
So, the benefit of using the CALLER scope is:
1. We don't have to rely on hard-coded name spaces.
2. Name spaces can be overridden at run time by calling context.
@Josh,
That's a good point. The only down side to that is that the Javascript would have to be downloaded N times (once per tag instance). Not a biggie, unless the code is large.
As far as a CFC, this could definitely work. I have no problems with CFCs generating output as long as that is their purpose.
@Ben: Ahh, I see what you mean about the calling context and using something like cfthread, nice trick :)
Perhaps you could also pass a reference to the namespace/scope to the custom tag rather than passing the namespace as a string? e.g.
<cf_demo2>
or...
<cf_demo2 scope="#variables.myNamespace#">
Then inside the custom tag:
<cfparam name="arguments.scope" default="#request#">
<cfset arguments.scope.jsRendered = true>
etc...
I think maybe the only flexibility you would lose is being able to create the full struct path in a single line of code, as per your caller scope example. Did that end up being a bug that you reported though? Is it something you can rely on, and has it been tested on other CFML engines?
Man I'm full of questions today, sorry :P
Errrr, that's supposed to be attributes.scope and not arguments.scope too :)
And now I realise that it's somewhat useless passing in a scope if it might not exist... Haha.
@Justin,
At first, I thought the CALLER scope issue was a bug; but, once I started to see how it could be leveraged, I had to assume that it was designed that way in for those very purposes (especially since a standard struct behavior would make things like that more difficult).
I think you could easily pass in a name-space. You would have to relax the CFParam type to be "any", but then once you had the value, you could test it for string or struct data type:
<cfif IsSimpleValue( ATTRIBUTES.NameSpace )>
. . . . // Use variable name to get create / access struct.
<cfelse>
. . . . // Name space passed in AS given struct.
</cfif>
I think that would be a really good modification, thanks!
I accomplish this before with creating a variable at the beginning of the custom tag which is nothing more than <cfset uniqueID = CreateUUID()>
Then I use that and append it to all my JS functions, and global JS variables.
Not the most efficient, but it worked...
I like this solution...
@David,
I am not sure I understand. If the tag has a UUID(), then how will other instances of the tag know that they are not supposed to execute parts?
It didn't, I created my custom tags with the CreateUUID() to be 100% self contained. They each had their own copy of everything they needed to run.
Hence: "Not the most efficient, but it worked..."
For example, if a custom tag needed to call a JS function, the JS Function would look like this.
function popUpWindow#uniqueID#(){ alert('1'); }
Although not the most efficient, I did gain 1 benefit from doing this, when all functions and vars had this unique name to it, I never had to worry about conflicts from any other libraries, or custom JS code...
In other words, I never thought of trying to be aware of other custom tags on a page, and I never even thought of thinking about a solution like you have been discussing. My idea was not a counter idea to what your discussing, simply what I did with my custom tags when I need to have multiple tags on the same page.
I like this solution much better.
@David,
Ahh, ok, gotcha. Sorry, I was not quite clear on that point.