Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at CFUNITED Express NYC (Apr. 2009) with: Nafisa Sabu

ColdFusion Basics : Nesting Custom Tags

By Ben Nadel on
Tags: ColdFusion

I am posting this here to help someone on the House of Fusion CF-Talk mailing list. This demonstrates a really simple nesting custom tag example. In this demo, there are two custom tags: list.cfm and item.cfm. The item.cfm tags go in the list.cfm to create either a horizontal or vertical layout:

  • <!--- Our parent list tag. --->
  • <cf_list align="horizontal">
  •  
  • <!--- A sub tag: item.cfm. --->
  • <cf_item>
  • Ashley Thomas
  • </cf_item>
  •  
  • <!--- A sub tag: item.cfm. --->
  • <cf_item>
  • Sarah Vivenzio
  • </cf_item>
  •  
  • </cf_list>

Before we get into the parent tag, let's explore the child tag first. Actually before we do that, let's just cover some nested tag basics:

  1. A tag can generate content if it has a close tag. That content is available in a variable THISTAG.GeneratedContent.
  2. The generated content of a tag is not available until the tag is running in execution mode: End. Think about it, if we are in the start tag, no content could possibly have been generated.
  3. Child tags associate themselves with parent tags.
  4. A parent tag DOES NOT know about its child (nested) tags until it is in its own End mode execution.

Ok, that being said, let's take a look at the child tag:

  • <!--- Kill extra output. --->
  • <cfsilent>
  •  
  • <!---
  • We only want to associate tag info with the parent in the
  • first run of the sub tag. Otherwise, we might end up
  • storing the info twice. Also, no need to validate
  • attributes twice, the first time is sufficient.
  • --->
  • <cfif (THISTAG.ExecutionMode EQ "Start")>
  •  
  • <!---
  • Associate this tag with the parent tag. We are
  • going to override the default value for data
  • collection. It is usually AssocAttribs, which is a
  • horrible name. We are going to store it in the
  • structure "Items". This will allow the attribute data
  • of the tag to be stored in parent tag.
  •  
  • In this case, "list.cfm" is our parent tag. We define
  • this association via the old school name "cf_list".
  • --->
  • <cfassociate
  • basetag="cf_list"
  • datacollection="Items"
  • />
  •  
  • <!--- Param tag attributes. --->
  • <cfparam
  • name="ATTRIBUTES.value"
  • type="string"
  • default=""
  • />
  •  
  • <!---
  • Param the trim value flag. We are defaulting this
  • to true. In that case, we will trim the value of
  • the GENERATED CONTENT (not the Value attribute).
  • --->
  • <cfparam
  • name="ATTRIBUTES.trimvalue"
  • type="boolean"
  • default="true"
  • />
  •  
  • </cfif>
  •  
  • <!---
  • Check to see if this tag as a closing tag. If it does,
  • then we might be sending the value of the generated
  • content instead of the value attribute.
  • --->
  • <cfif THISTAG.HasEndTag>
  •  
  • <!---
  • This tag might just be self closing (which would
  • be considered a closing end tag. In that case, we
  • won't have any length in our generated content
  • (the content between the opening and closing tags.
  • Check the length of the generated contet.
  • --->
  • <cfif Len( THISTAG.GeneratedContent )>
  •  
  • <!---
  • Since we have generated content, we are going
  • to be using that as our list item value.
  • Check to see if we need to trim this.
  • --->
  • <cfif ATTRIBUTES.trimvalue>
  •  
  • <!---
  • Trim value and save it into the attributes.
  • We don't need to store into attributes, but
  • we already have the value, so why not.
  • --->
  • <cfset ATTRIBUTES.value = THISTAG.GeneratedContent.Trim() />
  •  
  • <!--- Erase the generated content. --->
  • <cfset THISTAG.GeneratedContent = "" />
  •  
  • </cfif>
  •  
  • </cfif>
  •  
  • </cfif>
  •  
  • </cfsilent>

I am not going to comment too much here because the code sample itself is very well commented. The one thing I will touch upon is the CFAssociate tag. I am hard coding the parent tag "cf_list". This does NOT need to be hard coded. You can use the GetBaseTagList() method to make it more dynamic:

  • <cfassociate
  • basetag="#ListLast( GetBaseTagList() )#"
  • datacollection="Items"
  • />

I would NOT recommend this as you never really know where the parent tag name will show up in the base tag list. I am just noting this so you can let your imagination run wild.

Ok, now let's take a look at the parent tag:

  • <!--- Kill extra output. --->
  • <cfsilent>
  •  
  • <!---
  • Check to see if we are in the start mode of the tag. There
  • is no need to param any attributes after the start mode.
  • --->
  • <cfif (THISTAG.ExecutionMode EQ "Start")>
  •  
  • <!---
  • Param the align attribute. This till determine if we
  • show the list one after another in-line, or if the
  • list should be displayed as block elements.
  •  
  • Possible values:
  • - horizontal (default)
  • - vertical
  • --->
  • <cfparam
  • name="ATTRIBUTES.align"
  • type="string"
  • default="horizontal"
  • />
  •  
  • </cfif>
  •  
  • </cfsilent>
  •  
  • <!---
  • Since this is a parent tag that is designed to have child
  • tags, we can't really do anything until the child tags have
  • been defined. Therefore, we can only really work in the
  • End mode of execution.
  • --->
  • <cfif (THISTAG.ExecutionMode EQ "End")>
  •  
  • <!---
  • ASSERT:
  • At this point, we should have all the child tags
  • associated with this parent tag. As per the child tags,
  • all the attribute data should be in a structure:
  • THISTAG.Items.
  • --->
  •  
  • <!---
  • Now, we have to check to see how to display the items.
  • Vertically or horizontally?
  • --->
  • <cfif (ATTRIBUTES.align EQ "vertical")>
  •  
  • <!--- Display veritcally. --->
  •  
  • <!---
  • Loop over the Items array. Remember, this array
  • contains the attributes of the child tags. Remember
  • to use CFOutput tags as custom tags are NOT natural
  • CFOutput blocks.
  • --->
  • <cfoutput>
  •  
  • <cfloop
  • index="intI"
  • from="1"
  • to="#ArrayLen( THISTAG.Items )#"
  • step="1">
  •  
  • <!--- Output value. --->
  • <p>
  • Item #intI#: #THISTAG.Items[ intI ].value#
  • </p>
  •  
  • </cfloop>
  •  
  • </cfoutput>
  •  
  • <cfelse>
  •  
  • <!---
  • We are going with the default, which is vertical.
  • You might think the first CFIF clause should be the
  • default statement since the default is probably the
  • most often used. By making the default the ELSE
  • clause, we don't really have to validate the types
  • passed in. But that's not really here nor there.
  • --->
  •  
  • <!---
  • Loop over the Items array. Remember, this array
  • contains the attributes of the child tags. Remember
  • to use CFOutput tags as custom tags are NOT natural
  • CFOutput blocks.
  • --->
  • <cfoutput>
  •  
  • <cfloop
  • index="intI"
  • from="1"
  • to="#ArrayLen( THISTAG.Items )#"
  • step="1">
  •  
  • <!--- Output value. --->
  • Item #intI#: #THISTAG.Items[ intI ].value#
  •  
  • </cfloop>
  •  
  • </cfoutput>
  •  
  • </cfif>
  •  
  • </cfif>

Again, the code above is fairly well commented, so I will just let you take it in and process it. I know that the code samples here are not great, I will try to wrap this thing up in a ZIP and put it in the ColdFusion code snippets.

Tweet This Titillating read by @BenNadel - ColdFusion Basics : Nesting Custom Tags Thanks my man — you rock the party that rocks the body!


Looking For A New Job?

100% of job board revenue is donated to Kiva. Loans that change livesFind out more »

Reader Comments

Nice tutorial.

I have been experimenting with jQuery tabs. In one session, I used functions to wrap the code. I wanted to see if custom tags would be easier to use (for other users). This was very helpful in making the demo.

Reply to this Comment

@ben

> cfassociate

This tag is stupid.

Why would the parent tag just want to know what attributes were passed to the child?

How about content?

> GetBaseTagList()
> I would NOT recommend this as you never really know where the parent tag name will show up in the base tag list. I am just noting this so you can let your imagination run wild.

You _do_ know where the parent tag name is in the list.

It's sequential, as in an ancestral hierarchy.

A given tag's parent is always the second item in the array. The first item is the start tag for the given tag. That is, if you use start and end tags (why wouldn't you?).

It's actually a little complicated use this information to actually access the parent, however.

........................................

<!--- get ansestor tags --->
<cfset ancestorTagNames=listToArray(getBaseTagList())/>
<cfset ancestorTagNameInstances={}/>
<cfset ancestorTags=[]/>
<cfloop index="index" from="0" to="#(arrayLen(ancestorTagNames) - 1)#">
<cfset name=ancestorTagNames[index + 1]/>

<cfif (not(structKeyExists(ancestorTagNameInstances, name)))>
<cfset ancestorTagNameInstances[name]=[]/>
</cfif>
<cfset arrayAppend(ancestorTagNameInstances[name], (index + 1))/>

<cfset nameInstance=arrayLen(ancestorTagNameInstances[name])/>
<cfset data=getBaseTagData(name, nameInstance)/>
<cfset arrayAppend(ancestorTags, data)/>
</cfloop>

<!--- get parent tag --->
<cfif (arrayLen(ancestorTagNames) gt 1)>
<cfset hasParentTag=true/>
<cfset parentTag=ancestorTags[1 + 1]/>
<cfelse>
<cfset hasParentTag=false/>
</cfif>

<!--- do something with parent tag --->
<cfif (hasParentTag)>
<cfset arrayAppend(parentTag.childTags, variables)/>
</cfif>

........................................

Things I've with this:
- ASP.NET style master pages
- CFC-backed tags

Reply to this Comment

> [index + 1]

Sorry for the weird syntax.

I am still getting used to arrays starting at 1.

I am currently in denial about it.

Reply to this Comment

@Alex,

As far as CFAssociate, sometimes all the child tags need to do is collect attribute data. In that case, the parent tag only needs to get access to the attribute data of the children. This is quite common when you are using custom tags to create a sort of domain specific language for a feature of your website.

As for GetBaseTagList(), yes, they are in nested order; but, if you have multiple parent tags with the same name, then you might access one incorrectly.

Theoretically, you can always figure out *which* parent your are looking for, but it is certainly not *always* the second custom tag. In fact, with many nested situations, it is the last custom tag (of a certain kind) in the list.

Reply to this Comment

@ben

> which parent

I'd say there's only one. There are other levels, grandparents, great grandparents.

But shouldn't you let your parent talk to your grandparent?

I'm thinking error handling, event handling, et al. These things need to bubble up the chain.

How about this scenario.
........................................

<cf_masterpage>
<cf_masterpagepart id="title">Foo</cf_masterpagepart>
<cf_masterpagepart id="main">
<p>Foo boo goo.</p>
</cf_masterpagepart>
<cf_masterpagepart id="right">
Related:
<cf_list>
<cf_item>Foo</cf_item>
<cf_item>Boo</cf_item>
<cf_item>Goo</cf_item>
</cf_list>
</cf_masterpagepart>
</cf_masterpage>
........................................

You have a custom masterpage tag.

You have specific custom masterpagepart tags, which will replace associated parts in a master page template.

You have a custom list tag inside a masterpagepart.

The custom list tag need not ever know it's part of a masterpagepart, or ultimately a masterpage.

You may want to reuse that tag somewhere else.
........................................

<cf_page>
<div id="main">
<h1>Foo</h1>
<p>Foo boo goo.</p>
</div>
<div id="right">
Related:
<cf_list>
<cf_item>Foo</cf_item>
<cf_item>Boo</cf_item>
<cf_item>Goo</cf_item>
</cf_list>
</div>
</cf_page>
........................................

In fact, that's exactly how the built-in CF tags work.
........................................

<cfsavecontent>
<cfhttp>
<cfhttpparam/>
</cfhttp>
</cfsavecontent>
........................................

The http tag needs to know about its children, and the httpparam tags about their parent.

But the http tag doesn't need to know it's parent is savecontent.

In fact, the httpparam tag is all too redundant. How about some polymorphism, Macromedia/Adobe?
........................................

<cfheader/>
........................................

Parent is page (no parent), set response header.
........................................

<cfhttp>
<cfheader/>
</cfhttp>
........................................

Parent is http, set http request header.

I've been working on some polymorphic tags.
........................................

<cf_list type="ordered|unordered">
<cf_item/>
</cf_list>

<cf_sections>
<cf_item/>
</cf_sections>

<cf_dropdown>
<cf_item/>
</cf_dropdown>
........................................

-AH

Reply to this Comment

@Alex,

Yes, when you have very structured markup, there is only one logical parent tag; and, many of the tags don't even need to know about each other. But, you can start to get very abstract with content collection in which nesting can be N levels deep:

<cf_contentitem>
. . . . <cf_contentitem>
. . . . . . . . <cf_contentitem />
. . . . </cf_contentitem>
</cf_contentitem>

Now, of course, things like this *rarely* happen, but then do happen from time to time. And, when it happens, you have to be careful about which parent you need to talk to.

Now, just we are on the same page, at some point, you DO need to know about the tags you are working with. After all, if they are going to communicate, you need to understand who communicates with who. All I was really saying was that using GetBaseTagList() is not really a necessary way to do this. Rather, it makes more sense (and is generally more clear) to use GetBaseTagData() with the name of the parent you are talking to (and the optional index of the parent instance).

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
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.