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 »
11,243 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
Ben Nadel's Company - Epicenter Consulting Recent Blog Comments
May 22, 2013 at 12:44 PM
Ask Ben: Query Loop Inside CFScript Tags
In cf10, if you call a function that has: local.result = {}; local.result.msg = ""; local.svc = new query(); local.svc.setSQL("SELECT * FROM..."); local.obj = local.svc.exe ... read »
May 22, 2013 at 12:29 PM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
@Ben: What version of Java are you using? Also, did you test users.id to see what Java reports as the data type? I wonder if it's not a Java primitive data type, but getting returned as something ... read »
May 22, 2013 at 11:47 AM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
@Dana, Awesome - so it looks like this bug was fixed in ColdFusion 10. Thanks so much for double-checking that. ... read »
May 22, 2013 at 11:37 AM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
When I c&p and run on cf10, I get: Selected User IDs: 1,4 User 1 selected: YES - YES User 2 selected: NO - NO User 3 selected: NO - NO User 4 selected: YES - YES User 5 selected: NO - ... read »
May 22, 2013 at 11:27 AM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
@Tom, Good thought, but no dice. Both of these still exhibit the same behavior: users.id[ users.currentRow ] users[ "id" ][ users.currentRow ] It's just something whacky happening with ... read »
May 22, 2013 at 11:07 AM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
Could your problem be that "users.id" is actually an ARRAY, not a single value? Perhaps try it again with "users.id[1]" (I only have CF8 here at work). ... read »
May 22, 2013 at 7:52 AM
Nested Views, Routing, And Deep Linking With AngularJS
Hi, Just a quick thank you. As it happens, for my own purposes, the pending ui-router work being done in native angular is likely the one I'll adopt, but your exploration, code and documentation of ... read »
May 22, 2013 at 4:43 AM
How Do You Use The ColdFusion CFParam Tag?
'<cfparam>' or 'isDefined()and <cfset>' performs the same task.Is there any difference? ... read »
InVision App - Prototyping Made Beautiful With Prototyping Tools