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 the New York ColdFusion User Group (Dec. 2008) with: Clark Valberg

Building A Table With Nested ColdFusion Custom Tags

By Ben Nadel on
Tags: ColdFusion

As part of my ongoing research of ColdFusion custom tags, I have tried to build an HTML table using nested Table, Row, and Cell custom tags. I just wanted to get a sense of how you might relate a hierarchy of deeply nested tags. This was more complicated and less elegant that I was hoping it would be. But, for a first attempt, I think it taught me a lot and really pointed out where I need to think out a better implementation.

I created a basic table structure in the index page (index.cfm):

  • <!--- Impor the tag libraries. --->
  • <cfimport taglib="./" prefix="tag" />
  •  
  • <!--- Define table tags with nested rows and cells. --->
  • <tag:table>
  •  
  • <tag:row>
  • <tag:cell>
  • Name
  • </tag:cell>
  • <tag:cell>
  • Age
  • </tag:cell>
  • <tag:cell>
  • Mad Sexy
  • </tag:cell>
  • </tag:row>
  •  
  • <tag:row>
  • <tag:cell value="Libby" />
  • <tag:cell value="27" />
  • <tag:cell value="Yes" />
  • </tag:row>
  •  
  • <tag:row>
  • <tag:cell>
  • Kit
  • </tag:cell>
  • <tag:cell>
  • 24
  • </tag:cell>
  • <tag:cell>
  • Yes
  • </tag:cell>
  • </tag:row>
  •  
  • <tag:row>
  • <tag:cell value="Megan" />
  • <tag:cell value="32" />
  • <tag:cell value="No" />
  • </tag:row>
  •  
  • </tag:table>

Notice that the Cell custom tag can take the "Value" as either a tag attribute or as the contents of the tag itself.

Here is how the Table ColdFusion custom tag works:

  • <!---
  • Check to see which execution mode the tag is running in.
  • We won't have access to the child tag data until we are
  • in the End tag mode.
  • --->
  • <cfswitch expression="#THISTAG.ExecutionMode#">
  •  
  • <cfcase value="Start">
  •  
  • <!---
  • Create an array for holding the neseted Row tags.
  • This array will be manually populated by the Row
  • tags themselves.
  • --->
  • <cfset THISTAG.Rows = ArrayNew( 1 ) />
  •  
  • </cfcase>
  •  
  •  
  • <cfcase value="End">
  •  
  • <!---
  • At this point, all of the nested Row tags have
  • populated our Rows array. Now, let's Output
  • the table structure based on the nesting of
  • custom Row and Cell tags.
  • --->
  • <cfoutput>
  •  
  • <table cellspacing="1" cellpadding="3" border="1">
  • <!--- Loop over rows. --->
  • <cfloop
  • index="intRow"
  • from="1"
  • to="#ArrayLen( THISTAG.Rows )#"
  • step="1">
  •  
  • <!---
  • Each row contains an array of cells. Let's
  • get a short-hand reference to that array
  • of cells so that our subsequent code lines
  • are shorter and easier to read.
  • --->
  • <cfset arrCells = THISTAG.Rows[ intRow ].Cells />
  •  
  • <tr>
  • <!--- Loop over cells in this row. --->
  • <cfloop
  • index="intCell"
  • from="1"
  • to="#ArrayLen( arrCells )#"
  • step="1">
  •  
  • <td>
  • #arrCells[ intCell ].Data.Value#
  • </td>
  •  
  • </cfloop>
  • </tr>
  •  
  • </cfloop>
  • </table>
  •  
  • </cfoutput>
  •  
  • </cfcase>
  •  
  • </cfswitch>

The Table tag keeps a running Array of the rows (nested Row tags) that are defined within its open and close tags. The table tag doesn't actually modify this array at all. Each row adds itself to this array. I would have like to do this sort of thing via a Table-scoped UDF, but it seems that dynamic scope binding will not make this easy. In the close tag of the table, we then just output the nested row and cell data in an HTML table.

The Row ColdFusion custom tag looks like this:

  • <!---
  • Check to see which execution mode the tag is running in.
  • We won't have access to the child tag data until we are
  • in the End tag mode.
  • --->
  • <cfswitch expression="#THISTAG.ExecutionMode#">
  •  
  • <cfcase value="Start">
  •  
  • <!--- Get the parent Table tag. --->
  • <cfset THISTAG.Parent = GetBaseTagData( "cf_table" ) />
  •  
  • <!---
  • Create an array for holding the neseted Cell tags.
  • This array will be manually populated by the Cell
  • tags themselves.
  • --->
  • <cfset THISTAG.Cells = ArrayNew( 1 ) />
  •  
  • </cfcase>
  •  
  •  
  • <cfcase value="End">
  •  
  • <!---
  • Now that we are done processing this Row tag, let's
  • add it to the Rows array of the parent Table tag. We
  • are just going to add the THISTAG pointer which has
  • the Cells array that we will need.
  • --->
  • <cfset ArrayAppend(
  • THISTAG.Parent.THISTAG.Rows,
  • THISTAG
  • ) />
  •  
  • </cfcase>
  •  
  • </cfswitch>

The Row gets a pointer to the parent table tag using the GetBaseTagData() method call. Like the table, the row also creates an array to hold pointers to its nested tags. Of course, instead of storing Row pointers, this row is storing Cell pointers. Once the tag is done processing, it reaches back up into the parent (Table) and appends itself to the Rows array.

The Cell ColdFusion custom tag looks like this:

  • <!---
  • Check to see which execution mode the tag is running in.
  • We won't have access to the child tag data until we are
  • in the End tag mode.
  • --->
  • <cfswitch expression="#THISTAG.ExecutionMode#">
  •  
  • <cfcase value="Start">
  •  
  • <!---
  • The value attribute can be overriden by the tag's
  • generated content. In the End mode, we will check
  • to see if we have any content.
  • --->
  • <cfparam
  • name="ATTRIBUTES.Value"
  • type="string"
  • default=""
  • />
  •  
  •  
  • <!--- Get the parent Row tag. --->
  • <cfset THISTAG.Parent = GetBaseTagData( "cf_row" ) />
  •  
  •  
  • <!---
  • Create a data structure for this tag. This is where
  • we will store all the data that should / can be
  • referenced by other tags (via our manual setting).
  • --->
  • <cfset THISTAG.Data = StructNew() />
  •  
  • <!--- Set the default data values. --->
  • <cfset THISTAG.Data.Value = ATTRIBUTES.Value />
  •  
  • </cfcase>
  •  
  •  
  • <cfcase value="End">
  •  
  • <!---
  • Check to see if we have any generated content. If
  • this tag doesn't have an end tag, then it won't
  • even matter (it will just use the ATTRIBUTES value
  • defined above).
  • --->
  • <cfif THISTAG.GeneratedContent.Trim().Length()>
  •  
  • <!---
  • Since the user did provide generated content,
  • then we will use this to override the Value
  • attribute.
  • --->
  • <cfset THISTAG.Data.Value = THISTAG.GeneratedContent.Trim() />
  •  
  • <!---
  • Now that we have stored the generated content,
  • we can clear it (the rendering of it will be
  • handled by one of the parent tags).
  • --->
  • <cfset THISTAG.GeneratedContent = "" />
  •  
  • </cfif>
  •  
  •  
  • <!---
  • Now that we are done processing this Cell tag, let's
  • add it to the Cells array of the parent Row tag. We
  • are just going to add the THISTAG pointer which has
  • the Data struct that we will need.
  • --->
  • <cfset ArrayAppend(
  • THISTAG.Parent.THISTAG.Cells,
  • THISTAG
  • ) />
  •  
  • </cfcase>
  •  
  • </cfswitch>

This tag is longer than the other two just because it can handle a Value tag attribute but also plays quite nicely with the tag's generated content. The Cell, like the Row, once done processing, reaches up into its parent tag (Row) and appends itself to the Cells array.

Running the index page above, we get the following output:


 
 
 

 
Nested ColdFusion Custom Tag Table Output  
 
 
 

The Table tag was able to iterate over its stored Row data and then each Row's stored Cell data in order to output this table. Now, while the objective was accomplished, I have to say that I am much less than pleased with how it was done. Something about it just doesn't sit right with me. For one, I am not sure that I like that the Table tag iterates over both the Rows and the Cells; I think I would like it more if the Table just rendered the Rows and then each Row as able to render its own Cells. Divide and conquer, that's the way to go.

However, since I can't do something like Row.Render(), as this is not an object oriented programming approach, I am not sure how else to do it. I suppose that I could render in a top-down fashion - start the HTML table in the open Table tag, let each row render as it loads, let each cell render as it loads, and then just close the HTML table in the close Table tag. That would be totally fine for this situation... but this situation is quite simple. What happens when I need to fully collect data before I manipulate it?

Maybe for something that involves a lot of complex data collection / manipulation (as is the task that I am preparing for), I could use a combination of ColdFusion custom tags and ColdFusion components. Maybe the root tag holds a pointer to an instantiated CFC; then, each nested tag gets a pointer to this CFC which is where it stores the data. In my example, the ColdFusion custom tags act as BOTH data collectors and data persisters. What if the custom tags were just used for data collection and relied on a referenced CFC to persist the data. This could / would be a very nice separation of concerns and, I think, truly leverages what both constructs are good at.

Clearly, further exploration is need before I can embark on anything too complex.




Reader Comments

Hey Ben.

Awesome article, thanks for taking the time to write it. Any ideas on how to pass a query object to the table tag to populate the rows and cells dynamically? I tried a cfloop of an attributes.queryname around your row loop so that it would repeat the row/cell process for each record in the query, and it does do that, but i can't seem to get the cell data that I call outside the custom tag itself [tag:cell]#queryname.column#[/tag:cell] to move past the first record. currentrow always stays 1. Any ideas?

@Tyler,

You wouldn't pass the query to the table tag. What you'd want to do is use the query to build the tags:

<tag:table>
<cfloop query="qTest">
<tag:row>
<tag:cell>#qTest.name#</tag:cell>
<tag:cell>#qTest.hair#</tag:cell>
<tag:cell>#qTest.hotness#</tag:cell>
</tag:row>
</cfloop>
</tag:table>

Ah, I was hoping to be able to do it the other way somehow; being able to pass the query to the table tag and let the tag figure it out. Oh well. Thank you for responding. I read your blog all the time btw. Great info. Thanks again.

@Tyler,

Certainly, you could alter the Table tag to be able to take a query, but then I think you making it into a completely different tag. Not to say that that is a bad thing, just that it's different from the original intent. You could develop a custom tag that's like:

<table:tag query="qData" />

And just have that whip up a table structure.

Also, glad that you read the blog :) Thanks for the positive feedback.

Hi Ben, I was about to make the Custom tag and Just came across your article

Great one..

I was expecting my tag to be something like this

<cf_table cell="5" row="5" isheader="yes" isfooter"yes" extra="{Pass as a structure}" query="optional" paging="yes/no" maxrecords="10-only needed if paging is defined">

and loop over the structure in the custom tag

i hope i am not going in wrong direction