Building A Table With Nested ColdFusion Custom Tags And A ColdFusion Component

Posted April 24, 2007 at 8:15 AM by Ben Nadel

Tags: ColdFusion

Previously, I tried to build a table structure using just nested ColdFusion custom tags. In that scenario, the base Table tag stored the rows in local structures and the Row tag stored the cells in a local structure. I didn't quite like the way the code was working. For this attempt, I am aiming for the same outcome, however, instead of storing the data directly, the base Table tag will store a ColdFusion component. This CFC will be responsible for maintain an internal data structure that represents the rows and cells of the table.

The idea here is that the ColdFusion custom tags act as data collectors - they merely execute and figure out what data they have. The ColdFusion component, Data.cfc, is responsible for accepting data, knowing which row or cell to apply it to, and maintaining an internal data structure.

The Data.cfc ColdFusion component is quite simple. It keeps an array of arrays and has a method for adding a row, adding a cell, and getting the rows array:

  • <cfcomponent
  • hint="Stores data for custom tags.">
  •  
  • <!---
  • Run pseudo constructor to set up data structures
  • and set default data values. This will run when
  • the CFC is instantiated.
  • --->
  •  
  • <!---
  • Set up an instance structure to hold instances
  • related data.
  • --->
  • <cfset VARIABLES.Instance = StructNew() />
  •  
  • <!---
  • Set up an array to keep track of data rows. Our
  • table is going to be constructed using an array of
  • arrays for rows of cells.
  • --->
  • <cfset VARIABLES.Instance.Rows = ArrayNew( 1 ) />
  •  
  •  
  • <cffunction
  • name="Init"
  • access="public"
  • returntype="any"
  • output="false"
  • hint="Returns an initialized data instance.">
  •  
  • <!--- Return This reference.--->
  • <cfreturn THIS />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="AddCell"
  • access="public"
  • returntype="void"
  • output="false"
  • hint="Adds the current cell value to the current row.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="Cell"
  • type="any"
  • required="true"
  • hint="Any object that implements the ToString() interface."
  • />
  •  
  • <!---
  • Check to see if we have a current row. If we
  • do not, then add a row first.
  • --->
  • <cfif NOT ArrayLen( VARIABLES.Instance.Rows )>
  • <cfset THIS.AddRow() />
  • </cfif>
  •  
  •  
  • <!--- Add the Cell value to the current row. --->
  • <cfset ArrayAppend(
  • VARIABLES.Instance.Rows[ ArrayLen( VARIABLES.Instance.Rows ) ],
  • ARGUMENTS.Cell
  • ) />
  •  
  • <!--- Return out. --->
  • <cfreturn />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="AddRow"
  • access="public"
  • returntype="void"
  • output="false"
  • hint="Adds a row to the current table.">
  •  
  • <!---
  • Append a row to the rows array. Since we are going
  • to model our table rows/cells as an array of
  • arrays, we need to append an empty array to our
  • rows array.
  • --->
  • <cfset ArrayAppend(
  • VARIABLES.Instance.Rows,
  • ArrayNew( 1 )
  • ) />
  •  
  • <!--- Return out. --->
  • <cfreturn />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="GetRows"
  • access="public"
  • returntype="array"
  • output="false"
  • hint="Returns a copy of the rows array.">
  •  
  • <cfreturn VARIABLES.Instance.Rows />
  • </cffunction>
  •  
  • </cfcomponent>

Our index page (index.cfm) has not changed at all. It defines all the table, rows, and cell custom tags:

  • <!--- Impor the tag libraries. --->
  • <cfimport taglib="./" prefix="tag" />
  •  
  • <!--- Define table tags. --->
  • <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>

What has changed is our custom tags. Here, the table tag (table.cfm) now instantiates the Data.cfc when it starts to execute:

  • <!---
  • 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 instance of the Data ColdFusion
  • component. This component will keep track of our
  • rows and cells. Each child tag will alter this
  • component appropriately.
  • --->
  • <cfset VARIABLES.Data = CreateObject(
  • "component",
  • "Data"
  • ).Init()
  • />
  •  
  • </cfcase>
  •  
  •  
  • <cfcase value="End">
  •  
  • <!---
  • At this point, all of the nested Row tags have
  • populated the Data component. Now, let's Output
  • the table structure based on the nesting of
  • custom Row and Cell tags.
  • --->
  •  
  • <!--- Get the rows structure that has been created. --->
  • <cfset VARIABLES.Rows = VARIABLES.Data.GetRows() />
  •  
  • <cfoutput>
  •  
  • <table cellspacing="1" cellpadding="3" border="1">
  • <!--- Loop over rows. --->
  • <cfloop
  • index="intRow"
  • from="1"
  • to="#ArrayLen( VARIABLES.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 = VARIABLES.Rows[ intRow ] />
  •  
  • <tr>
  • <!--- Loop over cells in this row. --->
  • <cfloop
  • index="intCell"
  • from="1"
  • to="#ArrayLen( arrCells )#"
  • step="1">
  •  
  • <td>
  • #ToString( arrCells[ intCell ] )#
  • </td>
  •  
  • </cfloop>
  • </tr>
  •  
  • </cfloop>
  • </table>
  •  
  • </cfoutput>
  •  
  • </cfcase>
  •  
  • </cfswitch>

As you can see, it creates the Data component. Then, when the tag is done executing, instead of looping over its own Rows array, it grabs the Rows array out of the data component and renders the table.

The row and cell ColdFusion custom tags have changed slightly, but not all that much. They both still get pointers to the parent tags, but instead of adding themselves to the parent's data array, they are now both invoking methods of the Data ColdFusion component.

Here is the row tag (row.cfm):

  • <!---
  • 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.Table = GetBaseTagData( "cf_table" ) />
  •  
  • <!---
  • Add a row to the data collector that is stored
  • in the table (base) tag.
  • --->
  • <cfset THISTAG.Table.Data.AddRow() />
  •  
  • </cfcase>
  •  
  • </cfswitch>

Here is the cell tag (cell.cfm):

  • <!---
  • 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 table tag. We don't care about
  • which row we are in since the Data structure we are
  • referencing below will take care of that logic.
  • --->
  • <cfset THISTAG.Table = GetBaseTagData( "cf_table" ) />
  •  
  • </cfcase>
  •  
  •  
  • <cfcase value="End">
  •  
  • <!---
  • Now, we need to add a cell value to the data
  • collector that is being stored in the base Table
  • tag. Check to see if we have any generated content.
  • --->
  • <cfif THISTAG.GeneratedContent.Trim().Length()>
  •  
  • <!---
  • Since the user did provide generated content,
  • then we will use this instead of the Value
  • tag attribute.
  • --->
  • <cfset THISTAG.Table.Data.AddCell(
  • 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 = "" />
  •  
  • <cfelse>
  •  
  • <!---
  • Since the user did not provide any generated
  • content, just use the tag Value attribute as
  • the value of this cell.
  • --->
  • <cfset THISTAG.Table.Data.AddCell(
  • ATTRIBUTES.Value
  • ) />
  •  
  • </cfif>
  •  
  • </cfcase>
  •  
  • </cfswitch>

Running the above, we get the same output as we did in our previous example:


 
 
 

 
 
 
 
 

I like this method better than the previous. There was something very cumbersome about having to maintain a table data structure in the parent tag and in the row tag; it just felt very hacky. Having the table, row, and cell tags all call this centralized Data ColdFusion component just feels like a slightly more elegant solution.




Reader Comments

jon
Jul 13, 2007 at 5:38 PM // reply »
1 Comments

why?


Jul 13, 2007 at 5:47 PM // reply »
10,640 Comments

To learn the ins and outs of working with ColdFusion custom tags. It was an exploration, not a suggestion as to how to make tables.


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 »