GetBaseTagData() Works Differently In Adobe ColdFusion 2018 And Lucee CFML 5.3.7.47
For a few weeks now, I've been slowly rolling out my ColdFusion custom tag DSL for HTML emails at InVision. And, it's been great! So, yesterday, I decided to start using it on my blog for the comment emails. However, InVision runs on Lucee CFML and my blog runs on Adobe ColdFusion 2018; and, it turns out that the getBaseTagData()
function - which is a critical part of my DSL architecture - works completely different in the two ColdFusion runtimes.
The getBaseTagData()
function takes two arguments (from the documentation):
tagName
- Ancestor tag name for which to return data.ancestorLevels
- Number of ancestor levels to jump before returning data. The default value is 1 (closest ancestor).
Both Lucee CFML and Adobe ColdFusion use the same exact language in their documentation. But, it seems that the ancestorLevels
argument means something entirely different to each runtime. If I had to put it into words, here's how I would explain each implementation:
Lucee CFML -
ancestorLevels
- The number of custom tags in the hierarchy to skip before starting to search for the giventagName
. So, a0
would mean start searching in the current custom tag and a1
would mean start searching in the parent custom tag.Adobe ColdFusion -
ancestorLevels
- The instance number of the giventagName
to return while traversing the tag hierarchy. So, a1
would mean return the first instance of the giventagName
and a2
would mean return the second instance of the giventagName
.
To demonstrate this, I created a demo in which I have several ColdFusion custom tags nested with repetition. And then, at the bottom of the tag hierarchy, I try to walk up those tags, inspecting their attributes as I go:
<cfimport prefix="my" taglib="./" />
<!--- Three nested DIV tags. --->
<my:Div id="div:outer">
<my:Div id="div:middle">
<my:Div id="div:inner">
<!--- Three nested SECTION tags. --->
<my:Section id="section:outer">
<my:Section id="section:middle">
<my:Section id="section:inner">
<!--- Runtime-specific traversal tag. --->
<cfif server.keyExists( "Lucee" )>
<cf_InspectTagsLucee />
<cfelse>
<cf_InspectTagsACF />
</cfif>
</my:Section>
</my:Section>
</my:Section>
</my:Div>
</my:Div>
</my:Div>
As you can see, I have 3 nested <my:Div>
tags followed by 3 nested <my:Section>
tags followed by a runtime-specific inspector tag (which attempts to walk up the tree of custom tags looking for the id
attributes on each tag in the hierarchy).
First, let's look at the Lucee CFML inspector - it loops over the tag names in the getBaseTagList()
and uses the i
(iteration variable) to tell the getBaseTagData()
function where to start looking for the given tagName
:
<cfscript>
echo( "<h1> Lucee </h1>" );
loop
index = "i"
value = "tagName"
array = getBaseTagList().listToArray()
{
// In Lucee CFML, the second getBaseTagData() argument tells the runtime how many
// tags to SKIP OVER as it traverses the hierarchy looking for a matching name.
// As such, since we have multiple tags with the same name, we have to skip over
// tags we've already visited in order to get to the right tag.
// --
// NOTE: We are using -1 here because we don't want to skip the CURRENT tag when
// traversing the tag hierarchy. The current tag will be the first tag listed in
// the getBaseTagList() return.
tagData = getBaseTagData( tagName, ( i - 1 ) );
id = ( tagData.attributes.id ?: "none" );
echo( "• #tagName# — ID: #id# <br />" );
}
exit method = "exitTag";
</cfscript>
Note that we are using ( i - 1 )
in the getBaseTagData()
because we don't want to skip over the current (inspector) tag. And, when we run this in Lucee CFML, we get the following output:
As you can see, we were able to walk up the ColdFusion custom tag hierarchy and correctly target each tag using getBaseTagData()
.
Ok, now let's look at the Adobe ColdFusion inspector. This implementation is completely different. Instead of just giving the runtime an ancestor level at which to start searching, we actually need to keep track of how many times we see each tag. Then, we have to increment that value so that we target the right instance:
<cfscript>
writeOutput( "<h1> Adobe ColdFusion </h1>" );
tagNames = getBaseTagList().listToArray();
// In Adobe ColdFusion, the second getBaseTagData() argument tells the runtime which
// INSTANCE of a tag to return. As such, since we have multiple tags with the same
// name, we have to keep track of how many times we've seen with a given name so that
// we can skip over those instances.
instanceCounters = {};
for ( tagName in tagNames ) {
// If we haven't seen a tag with this name yet, default to zero so that we can
// start incrementing the visitations.
if ( ! instanceCounters.keyExists( tagName ) ) {
instanceCounters[ tagName ] = 0;
}
// Increment the instance counter for this tag so that we either get the first
// instance (or the "next" instance if we have a non-zero index).
tagData = getBaseTagData( tagName, ++instanceCounters[ tagName ] );
id = ( tagData.attributes.id ?: "none" );
writeOutput( "• #tagName# — ID: #id# <br />" );
}
cfexit( method = "exitTag" );
</cfscript>
As you can see, the logic here is quite a bit more complicated since we have to worry about the instance count, not just the level offset of the search. But, when we run this implementation, we are able to generate the same output:
As you can see, we get the same output. Each <my:Section>
tag is inspect in series; and each <my:Div>
tag is inspected in series. We just have to do so using a completely different algorithm.
I know that Adobe ColdFusion existed first; and that Lucee CFML is supposed to be "compatible" with the Adobe implementation. But, personally, I prefer the Lucee CFML implementation of the getBaseTagData()
function - it feels more intuitive and requires less logic when you have nested tags with the same name.
This divergence, however, is going to make it hard for me to update my ColdFusion custom tag DSL for HTML emails so that it works in both runtimes. It's doable; just annoying. And, I've gotten so used to the Lucee CFML syntax for things like fat-arrows functions and Script-based tags, it's hard to go back to using the Adobe ColdFusion 2018 syntax. Maybe I can do it as a separate repository.
Want to use code from this post? Check out the license.
Reader Comments
Great research Ben, could you update the Lucee doc's with a compatibility note?
@Zac,
No problem -- can you point me to instructions on how to update the docs. I believe you did that before, but I don't remember where.
Two options,
there's a GitHub icon next to each section on every docs page, just click and edit via github on the web
or you can checkout the git repo and run it locally and via commandbox https://github.com/lucee/lucee-docs
@Zac,
Very cool, thank you.