Creating an Advanced Custom Tag
You're going to have to hold on tight for this ride because it's complicated at times, but it's worth the trip. Make notes in the margins of the book, build and run the code yourself as you read along, make a strong pot of coffeewhatever it takes to stay on point. When you're done, follow my instructions at the end of this chapter to expand on what you've learned, and you'll be well on your way to building complete libraries of custom tags that can be reused between applications with little or no modification.We're going to build another pair of nested custom tags that create a form. This form is going to actually manipulate data in a database and will adapt to being an Add form, an Edit form, a View form, or a Delete form. You'll leverage the bidirectional communication mechanism you just examined; you'll incorporate the flow control techniques you learned about in "Using CFEXIT to Control Custom Tag Processing Flow"; and you'll incorporate your own custom formStatus property into the custom tags so you can precisely control how and when the form is prepared and built.
Two Phases: Initial Page Display and PostBack
This form will post to itself so that all its functionality can be contained in the custom tags rather than split into a separate action page. To make this work, we'll need to split page processing into two phases: Initial Page Display, and the PostBack that occurs when the form posts back to itself. We have to know when these phases occur because the custom tags will need to display the form during the Initial Page Display and update the database during the PostBack phase.Figure 18.18 shows the process flows that occur during the Initial Page Display phase that presents the form to the user. The child tags don't have Inactive or End modes because they are not implemented as paired custom tags.
Figure 18.18. Processing flow of the Initial Page Display of a family of advanced nested custom tags.
[View full size image]

Figure 18.19. Processing flow of the PostBack of a family of advanced nested custom tags.
[View full size image]

Two Statuses: Preparing and Building
Think for a moment about displaying an Edit form populated with a record to be edited in the form. The first thing we have to do is determine which columns of data will populate the Edit form, and then we have to retrieve the record. All this work must be prepared before we even begin building the form itself, and we'll need information from the child tags in order to do it. Once the Preparing phase is finished, we go into the Building phase and build the form.
Using CFEXIT to Control Child Tag Processing Flow
Remember that <cfexit method="Loop"> jumps back into the tag's Inactive mode, which in turn passes control to the first child tag nested in the body of the parent tag. We're going to leverage this by calling <cfexit method="Loop"> as soon as we enter the building phase, so that the child tags can rerun and begin outputting fields into the parent form. (The first time we run the child tags, we're just getting their data so we can assemble an SQL statement to retrieve the record; the second time we run the child tags we're outputting the form fields themselves.)If this all sounds confusing, refer back to Figure 18.18 to get a feel for how all these mechanisms are going to work together. Building the tags step-by-step will also help you understand how it all works, so let's begin.
Creating an Add Form and a Form Field
We're going to build this form in layers so that we can concentrate on one piece of functionality at a time and not get overwhelmed. We'll start by building the Add form functionality first.First, though, let's create the list page in Listing 18.13. This will display the contents of the Company table and provide links to the form pages we'll build using the cf_DisplayForm and cf_DisplayField custom tags. For the sake of simplicity, create all your files in the Chapter18 directory you setup for this chapter.NOTEThe script to create the database for this chapter is in the Chapter18 directory of this book's CD-ROM. Use it to create a database named Chapter18, and then create a ColdFusion data source named Chapter18 that connects to this database.
Listing 18.13. CompanyList.cfmCreating a List of Companies
Now let's build the first part of the custom tag that generates a form field. Type the following code into a file named DisplayField.cfm:
<!--- Author: Adam Phillip Churvis -- ProductivityEnhancement.com --->
<cfquery name="companies" datasource="#Application.dbDSN#">
SELECT CompanyID, CompanyName, ZipCode FROM Company
</cfquery>
<cfif IsDefined("URL.message")>
<cfoutput>#URL.message#</cfoutput>
</cfif>
<table cellspacing="2" cellpadding="2" border="1">
<tr>
<td><b>Company Name</b></td>
<td><b>Zip Code</b></td>
<td>[<a href="CompanyAddForm.cfm">Add</a>]</td>
</tr>
<cfoutput query="companies">
<tr>
<td>#CompanyName# </td>
<td>#ZipCode# </td>
<td nowrap>
[<a href="CompanyViewForm.cfm?key=#CompanyID#">View</a>]
[<a href="CompanyEditForm.cfm?key=#CompanyID#">Edit</a>]
[<a href="CompanyDeleteForm.cfm?key=#CompanyID#">Delete</a>]
</td>
</tr>
</cfoutput>
</table>
<p><a href="index.cfm">Chapter 18 Home Page</a></p>
To create the field for an Add form, all we need is the name of the field (which matches the name of its corresponding database column), whether or not the column is a numeric data type (so we know how to formulate the SQL statement that will be sent to the database), and what to label the field. We're going to use bidirectional communication between the child and parent tags, so we'll set up that mechanism as well, from the beginning.Now let's build the first part of the Add form tag. Type the following code into a file named DisplayForm.cfm:
<!--- Ensure the tag's required attributes are passed in --->
<cfparam name="Attributes.fieldName" type="string">
<!--- Ensure the tag's optional attributes are defaulted if not passed in --->
<cfparam name="Attributes.fieldLabel" default="#Attributes.fieldName#"
type="string">
<cfparam name="Attributes.numericField" type="boolean" default="No">
<cfif ThisTag.ExecutionMode EQ "Start">
<!--- Make the parent tag's attributes available to this child tag --->
<cfset parentData = GetBaseTagData("CF_DISPLAYFORM")>
<!--- Associate this child tag with its parent tag --->
<cfassociate basetag="CF_DISPLAYFORM">
<cfoutput>
<tr>
<td>
#Attributes.fieldLabel#
</td>
<td>
<input type="text" name="#Attributes.fieldName#">
</td>
</tr>
</cfoutput>
</cfif>
The Start mode outputs the opening <form> tag and supportin220 markup, and the End mode outputs the script that focuses the cursor in the first visible form field, the Submit button, and the closing <form> tag and supportin220 markup. There's also a hyperlink back to the list page.Create a page named CompanyAddForm.cfm using the cf_DisplayForm and cf_DisplayField custom tags, so you can try out what you've created so far. Let's just add fields for a couple of the columns in the Company table for now:
<!--- Ensure the tag's required attributes are passed in --->
<cfparam name="Attributes.tableName" type="string">
<!--- Ensure the tag's optional attributes are defaulted if not passed in --->
<cfparam name="Attributes.tableLabel" type="string"
default="#Attributes.tableName#">
<cfoutput>
<cfif ThisTag.ExecutionMode EQ "Start"> <!--- Opening tag executed --->
<!--- Open the Form --->
<table cellspacing="2" cellpadding="2" border="1">
<form method="post" name="#Attributes.tableName#AddForm">
<tr>
<td colspan="2"><b>#Attributes.tableLabel# Add Form</b></td>
</tr>
<cfelse> <!--- Closing tag executed --->
<!--- Focus the first field --->
<script language="JavaScript" type="text/javascript">
for(var i = 0; i < document.forms[0].elements.length; i++)
{
if(document.forms[0].elements[i].type != "hidden")
{
document.forms[0].elements[i].focus();
break;
}
}
</script>
<!--- Close the Form --->
<tr>
<td> </td>
<td>
<input type="submit" name="Submit"
value="Add #Attributes.tableLabel#">
</td>
</tr>
</form>
</table>
<p><a href="#Attributes.tableName#List.cfm">#Attributes.tableLabel# List</a>
</p>
</cfif> <!--- End test for ExecutionMode --->
</cfoutput>
Run this page and you'll see a form that doesn't do anything other than display in the browser. We want to add the ability to insert the form data into the database. This is where we separate the form logic into an initial page-display phase and a PostBack phase. This is easy to do by simply placing a hidden field named PostBack into the form. When the page is initially displayed, there is nothing in the Form scope; but when the form is submitted, the Form scope will be populated with its fields and their values. So we simply test if PostBack is defined:
<cf_DisplayForm formType="Add" tableName="Company" keyColumn="CompanyID"
numericKey="Yes">
<cf_DisplayField fieldName="CompanyName" numericField="No">
<cf_DisplayField fieldName="ZipCode" numericField="No" fieldLabel="Zip Code">
</cf_DisplayForm>
[View full width]<!--- Ensure the tag's required attributes are passed in --->Look at the PostBack phase's End mode for a moment. Notice how we build the lists of columns and values from the child tag data passed into the parent tag's ThisTag.AssocAttribs array, and then execute the insert statement we build from these lists.Update your DisplayForm.cfm to match the preceding listing, and then give it a try. Your form should look just like Figure 18.20, and submitting the form should insert the record into the database and display the list shown in Figure 18.21.
<cfparam name="Attributes.tableName" type="string">
<!--- Ensure the tag's optional attributes are defaulted if not passed in --->
<cfparam name="Attributes.tableLabel" type="string"
default="#Attributes.tableName#">
<cfoutput>
<cfif NOT IsDefined("PostBack")> <!--- Not part of a PostBack --->
<cfif ThisTag.ExecutionMode EQ "Start"> <!--- Opening tag executed --->
<!--- Open the Form --->
<table cellspacing="2" cellpadding="2" border="1">
<form method="post" name="#Attributes.tableName#AddForm">
<input type="hidden" name="PostBack" value="1">
<tr>
<td colspan="2"><b>#Attributes.tableLabel# Add Form</b></td>
</tr>
<cfelse> <!--- Closing tag executed, not as part of a PostBack --->
<!--- Focus the first field --->
<script language="JavaScript" type="text/javascript">
for(var i = 0; i < document.forms[0].elements.length; i++)
{
if(document.forms[0].elements[i].type != "hidden")
{
document.forms[0].elements[i].focus();
break;
}
}
</script>
<!--- Close the Form --->
<tr>
<td> </td>
<td>
<input type="submit" name="Submit"
value="Add #Attributes.tableLabel#">
</td>
</tr>
</form>
</table>
<p>
<a href="#Attributes.tableName#List.cfm">#Attributes.tableLabel# List</a>
</p>
</cfif> <!--- End test for ExecutionMode --->
<cfelse> <!--- This page was requested as part of a PostBack --->
<cfif ThisTag.ExecutionMode EQ "Start"> <!--- Opening tag executed --->
<!--- An Add form doesn't need to do anything in Start mode --->
<cfelse> <!--- Closing tag executed --->
<!--- Assemble the lists of columns and their corresponding values --->
<cfset listOfColumns = ">
<cfset listOfValues = ">
<cfloop index="i" from="1" to="#ArrayLen(ThisTag.AssocAttribs)#">
<cfset listOfColumns = ListAppend(listOfColumns,
ThisTag.AssocAttribs[i].fieldName)>
<cfif ThisTag.AssocAttribs[i].numericField>
<cfset listOfValues = ListAppend(listOfValues,
Val(Form[ThisTag.AssocAttribs[i].fieldName]))>
<cfelse>
<cfset listOfValues = ListAppend(listOfValues,
"'#Replace(Form[ThisTag.AssocAttribs[i].fieldName],
"'", "''", "ALL")#'")>
</cfif>
</cfloop>
<cfquery name="insertRecord" datasource="#Application.dbDSN#">
INSERT INTO #Attributes.tableName#(
#listOfColumns#
)
VALUES(
#PreserveSingleQuotes(listOfValues)#
)
</cfquery>
<cflocation url="#Attributes.TableName#List.cfm?message=#Attributes.tableLabel# addedto database." addToken="No">
</cfif> <!--- End test for End mode --->
</cfif> <!--- End test for PostBack --->
</cfoutput>
Figure 18.20. The Add form so far.

Figure 18.21. We now have a working Add form.

Adding Edit Form Functionality
The Edit form needs a couple more items, including a new attribute to specify the type of form to create (Add or Edit), and the column that contains a unique key for each record in the table containing the data (this example only works with single-column keys).We also create an internal attribute named formStatus that tells both the form tag and its nested child tags which pass through the form is currently being processed. For now, we'll concentrate on just creating the Edit form:
[View full width]<!--- Ensure the tag's required attributes are passed in --->The formStatus helps us solve a tough chicken-and-egg problem: The parent tag can't retrieve a record to populate the form fields until it knows what form fields it needs to populate, but it can't know what form fields to populate until the form fields execute.So we set Attributes.formStatus to Preparing in the parent tag's Start mode. Then, when the parent tag is in Inactive mode while the child tags process, the child tag requests the parent data using GetBaseTagData() so it knows the parent tag is Preparing, and the child tags oblige by sending their own data up to their parent tag and not outputting any fields. Now the parent is in End mode, and we can retrieve the record.In sum, we retrieve the record while Preparing in the End mode of the Initial Page Display . Make sense? Give it a try. Create a page named CompanyEditForm.cfm containing the following code, and access it by clicking an Edit link on CompanyList.cfm:
<cfparam name="Attributes.formType" type="string">
<cfparam name="Attributes.tableName" type="string">
<cfparam name="Attributes.keyColumn" type="string">
<!--- Ensure the tag's optional attributes are defaulted if not passed in --->
<cfparam name="Attributes.numericKey" type="boolean" default="No">
<cfparam name="Attributes.tableLabel" type="string"
default="#Attributes.tableName#">
<cfoutput>
<cfif NOT IsDefined("PostBack")> <!--- Not part of a PostBack --->
<cfif ThisTag.ExecutionMode EQ "Start"> <!--- Opening tag executed --->
<!--- Tell the child tags that we're preparing the form --->
<cfset Attributes.formStatus = "Preparing">
<!--- Open the Form --->
<table cellspacing="2" cellpadding="2" border="1">
<form method="post" name="#Attributes.tableName##Attributes.formType#Form">
<input type="hidden" name="PostBack" value="1">
<tr>
<td colspan="2"><b>#Attributes.tableLabel# #Attributes.formType# Form</b>
</td>
</tr>
<!--- The Edit form requires a key to be passed --->
<cfif ListFindNoCase("Edit", Attributes.formType)>
<cfif NOT IsDefined("URL.key")>
<cflocation url="#Attributes.TableName#List.cfm" addToken="No">
</cfif>
<input type="hidden" name="Key" value="#URL.key#">
<cfelseif NOT ListFindNoCase("Add,Edit", Attributes.formType)>
<cfthrow message="The form was called using an illegal formType value."
type="CustomForm"
errorcode="60010">
</cfif>
<cfelse> <!--- Closing tag executed, not as part of a PostBack --->
<cfif Attributes.formStatus EQ "Preparing">
<cfif ListFindNoCase("Edit", Attributes.formType)>
<!--- Assemble the list of columns to retrieve --->
<cfset listOfColumns = ">
<cfloop index="i" from="1" to="#ArrayLen(ThisTag.AssocAttribs)#">
<cfset listOfColumns = ListAppend(listOfColumns,
ThisTag.AssocAttribs[i].fieldName)>
</cfloop>
<!--- Retrieve the record to display or edit --->
<cfquery name="retrieveRecord" datasource="#Application.dbDSN#">
SELECT
#listOfColumns#
FROM
#Attributes.tableName#
WHERE
<cfif Attributes.numericKey>
#Attributes.keyColumn# = #Val(URL.key)#
<cfelse>
#Attributes.keyColumn# = '#URL.key#'
</cfif>
</cfquery>
</cfif>
<!--- Tell the child tags that we're ready to build the form --->
<cfset Attributes.formStatus = "Building">
<!--- Pass execution to the first child tag --->
<cfexit method="LOOP">
</cfif>
<cfif Attributes.formStatus EQ "Building">
<cfif ListFindNoCase("Add,Edit", Attributes.formType)>
<!--- Focus the first field --->
<script language="JavaScript" type="text/javascript">
for(var i = 0; i < document.forms[0].elements.length; i++)
{
if(document.forms[0].elements[i].type != "hidden")
{
document.forms[0].elements[i].focus();
break;
}
}
</script>
</cfif>
<!--- Close the Form --->
<tr>
<td> </td>
<td>
<input type="submit" name="Submit"
value="#Attributes.formType# #Attributes.tableLabel#">
</td>
</tr>
</form>
</table>
<p>
<a href="#Attributes.tableName#List.cfm">#Attributes.tableLabel# List</a>
</p>
</cfif>
</cfif> <!--- End test for ExecutionMode --->
<cfelse> <!--- This page was requested as part of a PostBack --->
<cfif ThisTag.ExecutionMode EQ "Start"> <!--- Opening tag executed --->
<!--- An Add form doesn't need to do anything in Start mode --->
<cfelse> <!--- Closing tag executed --->
<!--- Assemble the lists of columns and their corresponding values --->
<cfset listOfColumns = ">
<cfset listOfValues = ">
<cfloop index="i" from="1" to="#ArrayLen(ThisTag.AssocAttribs)#">
<cfset listOfColumns = ListAppend(listOfColumns,
ThisTag.AssocAttribs[i].fieldName)>
<cfif ThisTag.AssocAttribs[i].numericField>
<cfset listOfValues = ListAppend(listOfValues,
Val(Form[ThisTag.AssocAttribs[i].fieldName]))>
<cfelse>
<cfset listOfValues = ListAppend(listOfValues,
"'#Replace(Form[ThisTag.AssocAttribs[i].fieldName],
"'", "''", "ALL")#'")>
</cfif>
</cfloop>
<cfquery name="insertRecord" datasource="#Application.dbDSN#">
INSERT INTO #Attributes.tableName#(
#listOfColumns#
)
VALUES(
#PreserveSingleQuotes(listOfValues)#
)
</cfquery>
<cflocation url="#Attributes.TableName#List.cfm?message=#Attributes.tableLabel# addedto database." addToken="No">
</cfif> <!--- End test for End mode --->
</cfif> <!--- End test for PostBack --->
</cfoutput>
[View full width]<!--- Ensure the tag's required attributes are passed in --->
<cf_DisplayForm formType="Edit" tableName="Company" keyColumn="CompanyID"
numericKey="Yes">
<cf_DisplayField fieldName="CompanyName" numericField="No">
<cf_DisplayField fieldName="ZipCode" numericField="No" fieldLabel="Zip Code">
</cf_DisplayForm>
<cfparam name="Attributes.formType" type="string">
<cfparam name="Attributes.tableName" type="string">
<cfparam name="Attributes.keyColumn" type="string">
<!--- Ensure the tag's optional attributes are defaulted if not passed in --->
<cfparam name="Attributes.numericKey" type="boolean" default="No">
<cfparam name="Attributes.tableLabel" type="string"
default="#Attributes.tableName#">
<cfoutput>
<cfif NOT IsDefined("PostBack")> <!--- Not part of a PostBack --->
<cfif ThisTag.ExecutionMode EQ "Start"> <!--- Opening tag executed --->
<!--- Tell the child tags that we're preparing the form --->
<cfset Attributes.formStatus = "Preparing">
<!--- Open the Form --->
<table cellspacing="2" cellpadding="2" border="1">
<form method="post" name="#Attributes.tableName##Attributes.formType#Form">
<input type="hidden" name="PostBack" value="1">
<tr>
<td colspan="2"><b>#Attributes.tableLabel# #Attributes.formType# Form</b>
</td>
</tr>
<!--- Edit, View, and Delete forms require a key to be passed --->
<cfif ListFindNoCase("Edit", Attributes.formType)>
<cfif NOT IsDefined("URL.key")>
<cflocation url="#Attributes.TableName#List.cfm" addToken="No">
</cfif>
<input type="hidden" name="Key" value="#URL.key#">
<cfelseif NOT ListFindNoCase("Add,Edit", Attributes.formType)>
<cfthrow message="The form was called using an illegal formType value."
type="CustomForm"
errorcode="60010">
</cfif>
<cfelse> <!--- Closing tag executed, not as part of a PostBack --->
<cfif Attributes.formStatus EQ "Preparing">
<cfif ListFindNoCase("Edit", Attributes.formType)>
<!--- Assemble the list of columns to retrieve --->
<cfset listOfColumns = ">
<cfloop index="i" from="1" to="#ArrayLen(ThisTag.AssocAttribs)#">
<cfset listOfColumns = ListAppend(listOfColumns,
ThisTag.AssocAttribs[i].fieldName)>
</cfloop>
<!--- Retrieve the record to display or edit --->
<cfquery name="retrieveRecord" datasource="#Application.dbDSN#">
SELECT
#listOfColumns#
FROM
#Attributes.tableName#
WHERE
<cfif Attributes.numericKey>
#Attributes.keyColumn# = #Val(URL.key)#
<cfelse>
#Attributes.keyColumn# = '#URL.key#'
</cfif>
</cfquery>
</cfif>
<!--- Tell the child tags that we're ready to build the form --->
<cfset Attributes.formStatus = "Building">
<!--- Pass execution to the first child tag --->
<cfexit method="LOOP">
</cfif>
<cfif Attributes.formStatus EQ "Building">
<cfif ListFindNoCase("Add,Edit", Attributes.formType)>
<!--- Focus the first field --->
<script language="JavaScript" type="text/javascript">
for(var i = 0; i < document.forms[0].elements.length; i++)
{
if(document.forms[0].elements[i].type != "hidden")
{
document.forms[0].elements[i].focus();
break;
}
}
</script>
</cfif>
<!--- Close the Form --->
<tr>
<td> </td>
<td>
<input type="submit" name="Submit"
value="#Attributes.formType# #Attributes.tableLabel#">
</td>
</tr>
</form>
</table>
<p>
<a href="#Attributes.tableName#List.cfm">#Attributes.tableLabel# List</a>
</p>
</cfif>
</cfif> <!--- End test for ExecutionMode --->
<cfelse> <!--- This page was requested as part of a PostBack --->
<cfif ThisTag.ExecutionMode EQ "Start"> <!--- Opening tag executed --->
<!--- Tell the child tags that we're preparing the form --->
<cfset Attributes.formStatus = "Preparing">
<cfelse> <!--- Closing tag executed --->
<cfswitch expression="#Attributes.formType#">
<cfcase value="Add">
<!--- Assemble the lists of columns and their corresponding values --->
<cfset listOfColumns = ">
<cfset listOfValues = ">
<cfloop index="i" from="1" to="#ArrayLen(ThisTag.AssocAttribs)#">
<cfset listOfColumns = ListAppend(listOfColumns,
ThisTag.AssocAttribs[i].fieldName)>
<cfif ThisTag.AssocAttribs[i].numericField>
<cfset listOfValues = ListAppend(listOfValues,
Val(Form[ThisTag.AssocAttribs[i].fieldName]))>
<cfelse>
<cfset listOfValues = ListAppend(listOfValues,
"'#Replace(Form[ThisTag.AssocAttribs[i].fieldName],
"'", "''", "ALL")#'")>
</cfif>
</cfloop>
<cfquery name="insertRecord" datasource="#Application.dbDSN#">
INSERT INTO #Attributes.tableName#(
#listOfColumns#
)
VALUES(
#PreserveSingleQuotes(listOfValues)#
)
</cfquery>
<cflocation url="#Attributes.TableName#List.cfm?message=#Attributes.tableLabel#

</cfcase>
<cfcase value="Edit">
<!--- Assemble the lists of columns and their corresponding values --->
<cfset listOfSetStatements = ">
<cfloop index="i" from="1" to="#ArrayLen(ThisTag.AssocAttribs)#">
<cfif ThisTag.AssocAttribs[i].numericField>
<cfset listOfSetStatements = ListAppend(listOfSetStatements,
ThisTag.AssocAttribs[i].fieldName & " = " &
Form[ThisTag.AssocAttribs[i].fieldName])>
<cfelse>
<cfset listOfSetStatements = ListAppend(listOfSetStatements,
ThisTag.AssocAttribs[i].fieldName & " = " &
"'#Replace(Form[ThisTag.AssocAttribs[i].fieldName], "'", "''",
"ALL")#'")>
</cfif>
</cfloop>
<cfquery name="updateRecord" datasource="#Application.dbDSN#">
UPDATE #Attributes.tableName#
SET #PreserveSingleQuotes(listOfSetStatements)#
<cfif Attributes.numericKey>
WHERE #Attributes.keyColumn# = #Form.Key#
<cfelse>
WHERE #Attributes.keyColumn# = '#Form.Key#'
</cfif>
</cfquery>
<cflocation url="#Attributes.TableName#List.cfm?message=#Attributes.tableLabel#

</cfcase>
</cfswitch>
</cfif> <!--- End test for End mode --->
</cfif> <!--- End test for PostBack --->
</cfoutput>
That may seem like a lot of code, but it really isn't when you think about everything this code does. Let's concentrate for now on the child tags' Preparing pass through the form.We start by opening the Edit form tag and redirecting requests that don't pass the required key parameter in the URL. If the URL contains the required key, control is passed to the child tags while the formStatus is set to Preparing. As you follow the explanation of this part of the code, refer to Figure 18.18:
We added a test for Attributes.formStatus EQ "Building" so that the child tag outputs form fields only when the parent tag is in the Building pass, and we added a test for Attributes.formType to output an input value attribute only when we're building an Edit form.Try it out, and you should have a form that looks like Figure 18.22, which updates the database as expected and as shown in Figure 18.23.
<!--- Ensure the tag's required attributes are passed in --->
<cfparam name="Attributes.fieldName" type="string">
<!--- Ensure the tag's optional attributes are defaulted if not passed in --->
<cfparam name="Attributes.fieldLabel" default="#Attributes.fieldName#"
type="string">
<cfparam name="Attributes.numericField" type="boolean" default="No">
<cfif ThisTag.ExecutionMode EQ "Start">
<!--- Make the parent tag's attributes available to this child tag --->
<cfset parentData = GetBaseTagData("CF_DISPLAYFORM")>
<cfif parentData.Attributes.formStatus EQ "Preparing">
<!--- Associate this child tag with its parent tag --->
<cfassociate basetag="CF_DISPLAYFORM">
</cfif>
<cfif parentData.Attributes.formStatus EQ "Building">
<cfoutput>
<tr>
<td>
#Attributes.fieldLabel#
</td>
<td>
<input type="text" name="#Attributes.fieldName#"
<cfif parentData.Attributes.formType EQ "Edit">
value="#parentData.retrieveRecord[Attributes.fieldName][1]#"
</cfif>>
</td>
</tr>
</cfoutput>
</cfif>
</cfif>
Figure 18.22. cf_DisplayForm and cf_DisplayField can now adapt to being either an Add or an Edit form.

Figure 18.23. The form can now also adapt to inserting for an Add form or updating for an Edit form.

Adding Delete Form Functionality
Look through the following code for the word "Delete" and you'll see how easy it was to add Delete form functionality to what we've already built:
[View full width]<!--- Ensure the tag's required attributes are passed in --->In DisplayField.cfm we now have to choose between outputting a form field for Add and Edit forms or a static value for Delete forms.
<cfparam name="Attributes.formType" type="string">
<cfparam name="Attributes.tableName" type="string">
<cfparam name="Attributes.keyColumn" type="string">
<!--- Ensure the tag's optional attributes are defaulted if not passed in --->
<cfparam name="Attributes.numericKey" type="boolean" default="No">
<cfparam name="Attributes.tableLabel" type="string"
default="#Attributes.tableName#">
<cfoutput>
<cfif NOT IsDefined("PostBack")> <!--- Not part of a PostBack --->
<cfif ThisTag.ExecutionMode EQ "Start"> <!--- Opening tag executed --->
<!--- Tell the child tags that we're preparing the form --->
<cfset Attributes.formStatus = "Preparing">
<!--- Open the Form --->
<table cellspacing="2" cellpadding="2" border="1">
<form method="post" name="#Attributes.tableName##Attributes.formType#Form">
<input type="hidden" name="PostBack" value="1">
<tr>
<td colspan="2"><b>#Attributes.tableLabel# #Attributes.formType# Form</b>
</td>
</tr>
<!--- Edit and Delete forms require a key to be passed --->
<cfif ListFindNoCase("Edit,Delete", Attributes.formType)>
<cfif NOT IsDefined("URL.key")>
<cflocation url="#Attributes.TableName#List.cfm" addToken="No">
</cfif>
<input type="hidden" name="Key" value="#URL.key#">
<cfelseif NOT ListFindNoCase("Add,Edit,Delete", Attributes.formType)>
<cfthrow message="The form was called using an illegal formType value."
type="CustomForm"
errorcode="60010">
</cfif>
<cfelse> <!--- Closing tag executed, not as part of a PostBack --->
<cfif Attributes.formStatus EQ "Preparing">
<cfif ListFindNoCase("Edit,Delete", Attributes.formType)>
<!--- Assemble the list of columns to retrieve --->
<cfset listOfColumns = ">
<cfloop index="i" from="1" to="#ArrayLen(ThisTag.AssocAttribs)#">
<cfset listOfColumns = ListAppend(listOfColumns,
ThisTag.AssocAttribs[i].fieldName)>
</cfloop>
<!--- Retrieve the record to display or edit --->
<cfquery name="retrieveRecord" datasource="#Application.dbDSN#">
SELECT
#listOfColumns#
FROM
#Attributes.tableName#
WHERE
<cfif Attributes.numericKey>
#Attributes.keyColumn# = #Val(URL.key)#
<cfelse>
#Attributes.keyColumn# = '#URL.key#'
</cfif>
</cfquery>
</cfif>
<!--- Tell the child tags that we're ready to build the form --->
<cfset Attributes.formStatus = "Building">
<!--- Pass execution to the first child tag --->
<cfexit method="LOOP">
</cfif>
<cfif Attributes.formStatus EQ "Building">
<cfif ListFindNoCase("Add,Edit", Attributes.formType)>
<!--- Focus the first field --->
<script language="JavaScript" type="text/javascript">
for(var i = 0; i < document.forms[0].elements.length; i++)
{
if(document.forms[0].elements[i].type != "hidden")
{
document.forms[0].elements[i].focus();
break;
}
}
</script>
</cfif>
<!--- Close the Form --->
<tr>
<td> </td>
<td>
<input type="submit" name="Submit"
value="#Attributes.formType# #Attributes.tableLabel#">
</td>
</tr>
</form>
</table>
<p>
<a href="#Attributes.tableName#List.cfm">#Attributes.tableLabel# List</a>
</p>
</cfif>
</cfif> <!--- End test for ExecutionMode --->
<cfelse> <!--- This page was requested as part of a PostBack --->
<cfif ThisTag.ExecutionMode EQ "Start"> <!--- Opening tag executed --->
<!--- Tell the child tags that we're preparing the form --->
<cfset Attributes.formStatus = "Preparing">
<cfelse> <!--- Closing tag executed --->
<cfswitch expression="#Attributes.formType#">
<cfcase value="Add">
<!--- Assemble the lists of columns and their corresponding values --->
<cfset listOfColumns = ">
<cfset listOfValues = ">
<cfloop index="i" from="1" to="#ArrayLen(ThisTag.AssocAttribs)#">
<cfset listOfColumns = ListAppend(listOfColumns,
ThisTag.AssocAttribs[i].fieldName)>
<cfif ThisTag.AssocAttribs[i].numericField>
<cfset listOfValues = ListAppend(listOfValues,
Val(Form[ThisTag.AssocAttribs[i].fieldName]))>
<cfelse>
<cfset listOfValues = ListAppend(listOfValues,
"'#Replace(Form[ThisTag.AssocAttribs[i].fieldName],
"'", "''", "ALL")#'")>
</cfif>
</cfloop>
<cfquery name="insertRecord" datasource="#Application.dbDSN#">
INSERT INTO #Attributes.tableName#(
#listOfColumns#
)
VALUES(
#PreserveSingleQuotes(listOfValues)#
)
</cfquery>
<cflocation url="#Attributes.TableName#List.cfm?message=#Attributes.tableLabel#added to database." addToken="No">
</cfcase>
<cfcase value="Edit">
<!--- Assemble the lists of columns and their corresponding values --->
<cfset listOfSetStatements = ">
<cfloop index="i" from="1" to="#ArrayLen(ThisTag.AssocAttribs)#">
<cfif ThisTag.AssocAttribs[i].numericField>
<cfset listOfSetStatements = ListAppend(listOfSetStatements,
ThisTag.AssocAttribs[i].fieldName & " = " &
Form[ThisTag.AssocAttribs[i].fieldName])>
<cfelse>
<cfset listOfSetStatements = ListAppend(listOfSetStatements,
ThisTag.AssocAttribs[i].fieldName & " = " &
"'#Replace(Form[ThisTag.AssocAttribs[i].fieldName], "'", "''",
"ALL")#'")>
</cfif>
</cfloop>
<cfquery name="updateRecord" datasource="#Application.dbDSN#">
UPDATE #Attributes.tableName#
SET #PreserveSingleQuotes(listOfSetStatements)#
<cfif Attributes.numericKey>
WHERE #Attributes.keyColumn# = #Form.Key#
<cfelse>
WHERE #Attributes.keyColumn# = '#Form.Key#'
</cfif>
</cfquery>
<cflocation url="#Attributes.TableName#List.cfm?message=#Attributes.tableLabel#updated in database." addToken="No">
</cfcase>
<cfcase value="Delete">
<cfquery name="deleteRecord" datasource="#Application.dbDSN#">
DELETE FROM #Attributes.tableName#
<cfif Attributes.numericKey>
WHERE #Attributes.keyColumn# = #Form.Key#
<cfelse>
WHERE #Attributes.keyColumn# = '#Form.Key#'
</cfif>
</cfquery>
<cflocation url="#Attributes.TableName#List.cfm?message=#Attributes.tableLabel#deleted from database." addToken="No">
</cfcase>
</cfswitch>
</cfif> <!--- End test for End mode --->
</cfif> <!--- End test for PostBack --->
</cfoutput>
Give it a try. Copy the contents of CompanyEditForm.cfm to a file named CompanyDeleteForm.cfm and change the value of the formType attribute to Delete. Then access this new file via a Delete link on CompanyList.cfm and try to delete a record or two.So now you're getting the hang of it, eh? Let's wrap things up by adding View form functionality to what we've already done.
<!--- Ensure the tag's required attributes are passed in --->
<cfparam name="Attributes.fieldName" type="string">
<!--- Ensure the tag's optional attributes are defaulted if not passed in --->
<cfparam name="Attributes.fieldLabel" default="#Attributes.fieldName#"
type="string">
<cfparam name="Attributes.numericField" type="boolean" default="No">
<cfif ThisTag.ExecutionMode EQ "Start">
<!--- Make the parent tag's attributes available to this child tag --->
<cfset parentData = GetBaseTagData("CF_DISPLAYFORM")>
<cfif parentData.Attributes.formStatus EQ "Preparing">
<!--- Associate this child tag with its parent tag --->
<cfassociate basetag="CF_DISPLAYFORM">
</cfif>
<cfif parentData.Attributes.formStatus EQ "Building">
<cfoutput>
<!--- Add and Edit forms output form fields --->
<cfif ListFindNoCase("Add,Edit", parentData.Attributes.formType)>
<tr>
<td>
#Attributes.fieldLabel#
</td>
<td>
<input type="text" name="#Attributes.fieldName#"
<cfif parentData.Attributes.formType EQ "Edit">
value="#parentData.retrieveRecord[Attributes.fieldName][1]#"
</cfif>>
</td>
</tr>
</cfif>
<!--- View and Delete forms output static values --->
<cfif ListFindNoCase("Delete", parentData.Attributes.formType)>
<tr>
<td>
#Attributes.fieldLabel#
</td>
<td>
#parentData.retrieveRecord[Attributes.fieldName][1]#
</td>
</tr>
</cfif>
</cfoutput>
</cfif>
</cfif>
Adding View Form Functionality
Technically, there's no such thing as a "View form" because it's only a display page for a record that is never submitted anywhere, but it's still a useful feature for some systems that don't want to display Edit forms for data that doesn't need to be changed.Look for the word "View" in Listings 18.14 and 18.15 to see what we've changed. These are the finished versions of the cf_DisplayForm and cf_DisplayField custom tags.
Listing 18.14. DisplayForm.cfmCompleted Form Custom Tag
[View full width]
<!--- Author: Adam Phillip Churvis -- ProductivityEnhancement.com --->
<!--- Displays either an add, edit, view, or delete form --->
<!--- Ensure the tag's required attributes are passed in --->
<cfparam name="Attributes.formType" type="string">
<cfparam name="Attributes.tableName" type="string">
<cfparam name="Attributes.keyColumn" type="string">
<!--- Ensure the tag's optional attributes are defaulted if not passed in --->
<cfparam name="Attributes.numericKey" type="boolean" default="No">
<cfparam name="Attributes.tableLabel" type="string"
default="#Attributes.tableName#">
<cfoutput>
<cfif NOT IsDefined("PostBack")> <!--- Not part of a PostBack --->
<cfif ThisTag.ExecutionMode EQ "Start"> <!--- Opening tag executed --->
<!--- Tell the child tags that we're preparing the form --->
<cfset Attributes.formStatus = "Preparing">
<!--- Open the Form --->
<table cellspacing="2" cellpadding="2" border="1">
<form method="post" name="#Attributes.tableName##Attributes.formType#Form">
<input type="hidden" name="PostBack" value="1">
<tr>
<td colspan="2"><b>#Attributes.tableLabel# #Attributes.formType# Form</b>
</td>
</tr>
<!--- Edit, View, and Delete forms require a key to be passed --->
<cfif ListFindNoCase("Edit,View,Delete", Attributes.formType)>
<cfif NOT IsDefined("URL.key")>
<cflocation url="#Attributes.TableName#List.cfm" addToken="No">
</cfif>
<input type="hidden" name="Key" value="#URL.key#">
<cfelseif NOT ListFindNoCase("Add,Edit,View,Delete", Attributes.formType)>
<cfthrow message="The form was called using an illegal formType value."
type="CustomForm"
errorcode="60010">
</cfif>
<cfelse> <!--- Closing tag executed, not as part of a PostBack --->
<cfif Attributes.formStatus EQ "Preparing">
<cfif ListFindNoCase("Edit,View,Delete", Attributes.formType)>
<!--- Assemble the list of columns to retrieve --->
<cfset listOfColumns = ">
<cfloop index="i" from="1" to="#ArrayLen(ThisTag.AssocAttribs)#">
<cfset listOfColumns = ListAppend(listOfColumns,
ThisTag.AssocAttribs[i].fieldName)>
</cfloop>
<!--- Retrieve the record to display or edit --->
<cfquery name="retrieveRecord" datasource="#Application.dbDSN#">
SELECT
#listOfColumns#
FROM
#Attributes.tableName#
WHERE
<cfif Attributes.numericKey>
#Attributes.keyColumn# = #Val(URL.key)#
<cfelse>
#Attributes.keyColumn# = '#URL.key#'
</cfif>
</cfquery>
</cfif>
<!--- Tell the child tags that we're ready to build the form --->
<cfset Attributes.formStatus = "Building">
<!--- Pass execution to the first child tag --->
<cfexit method="LOOP">
</cfif>
<cfif Attributes.formStatus EQ "Building">
<cfif ListFindNoCase("Add,Edit", Attributes.formType)>
<!--- Focus the first field --->
<script language="JavaScript" type="text/javascript">
for(var i = 0; i < document.forms[0].elements.length; i++)
{
if(document.forms[0].elements[i].type != "hidden")
{
document.forms[0].elements[i].focus();
break;
}
}
</script>
</cfif>
<!--- Close the Form --->
<cfif ListFindNoCase("Add,Edit,Delete", Attributes.formType)>
<tr>
<td> </td>
<td>
<input type="submit" name="Submit"
value="#Attributes.formType# #Attributes.tableLabel#">
</td>
</tr>
</cfif>
</form>
</table>
<p>
<a href="#Attributes.tableName#List.cfm">#Attributes.tableLabel# List</a>
</p>
</cfif>
</cfif> <!--- End test for ExecutionMode --->
<cfelse> <!--- This page was requested as part of a PostBack --->
<cfif ThisTag.ExecutionMode EQ "Start"> <!--- Opening tag executed --->
<!--- Tell the child tags that we're preparing the form --->
<cfset Attributes.formStatus = "Preparing">
<cfelse> <!--- Closing tag executed --->
<cfswitch expression="#Attributes.formType#">
<cfcase value="Add">
<!--- Assemble the lists of columns and their corresponding values --->
<cfset listOfColumns = ">
<cfset listOfValues = ">
<cfloop index="i" from="1" to="#ArrayLen(ThisTag.AssocAttribs)#">
<cfset listOfColumns = ListAppend(listOfColumns,
ThisTag.AssocAttribs[i].fieldName)>
<cfif ThisTag.AssocAttribs[i].numericField>
<cfset listOfValues = ListAppend(listOfValues,
Val(Form[ThisTag.AssocAttribs[i].fieldName]))>
<cfelse>
<cfset listOfValues = ListAppend(listOfValues,
"'#Replace(Form[ThisTag.AssocAttribs[i].fieldName], "'", "''",
"ALL")#'")>
</cfif>
</cfloop>
<cfquery name="insertRecord" datasource="#Application.dbDSN#">
INSERT INTO #Attributes.tableName#(
#listOfColumns#
)
VALUES(
#PreserveSingleQuotes(listOfValues)#
)
</cfquery>
<cflocation url="#Attributes.TableName#List.cfm?message=#Attributes.tableLabel#added to database." addToken="No">
</cfcase>
<cfcase value="Edit">
<!--- Assemble the lists of columns and their corresponding values --->
<cfset listOfSetStatements = ">
<cfloop index="i" from="1" to="#ArrayLen(ThisTag.AssocAttribs)#">
<cfif ThisTag.AssocAttribs[i].numericField>
<cfset listOfSetStatements = ListAppend(listOfSetStatements,
ThisTag.AssocAttribs[i].fieldName & " = " &
Form[ThisTag.AssocAttribs[i].fieldName])>
<cfelse>
<cfset listOfSetStatements = ListAppend(listOfSetStatements,
ThisTag.AssocAttribs[i].fieldName & " = " &
"'#Replace(Form[ThisTag.AssocAttribs[i].fieldName], "'", "''",
"ALL")#'")>
</cfif>
</cfloop>
<cfquery name="updateRecord" datasource="#Application.dbDSN#">
UPDATE #Attributes.tableName#
SET #PreserveSingleQuotes(listOfSetStatements)#
<cfif Attributes.numericKey>
WHERE #Attributes.keyColumn# = #Form.Key#
<cfelse>
WHERE #Attributes.keyColumn# = '#Form.Key#'
</cfif>
</cfquery>
<cflocation url="#Attributes.TableName#List.cfm?message=#Attributes.tableLabel#updated in database." addToken="No">
</cfcase>
<cfcase value="Delete">
<cfquery name="deleteRecord" datasource="#Application.dbDSN#">
DELETE FROM #Attributes.tableName#
<cfif Attributes.numericKey>
WHERE #Attributes.keyColumn# = #Form.Key#
<cfelse>
WHERE #Attributes.keyColumn# = '#Form.Key#'
</cfif>
</cfquery>
<cflocation url="#Attributes.TableName#List.cfm?message=#Attributes.tableLabel#deleted from database." addToken="No">
</cfcase>
</cfswitch>
</cfif> <!--- End test for End mode --->
</cfif> <!--- End test for PostBack --->
</cfoutput>
Listing 18.15. DisplayField.cfmCompleted Field Custom Tag
How did this exercise feel for you? Advanced custom tags is not an easy subject to master, so don't feel bad if this chapter was a challenge. This is about as advanced as custom tag architecture gets, but you'll be glad you invested the time to build and understand it.
<!--- Author: Adam Phillip Churvis -- ProductivityEnhancement.com --->
<!--- Outputs either a text input or a static value depending upon the type of
form that contains it --->
<!--- Ensure the tag's required attributes are passed in --->
<cfparam name="Attributes.fieldName" type="string">
<!--- Ensure the tag's optional attributes are defaulted if not passed in --->
<cfparam name="Attributes.fieldLabel" default="#Attributes.fieldName#"
type="string">
<cfparam name="Attributes.numericField" type="boolean" default="No">
<cfif ThisTag.ExecutionMode EQ "Start">
<!--- Make the parent tag's attributes available to this child tag --->
<cfset parentData = GetBaseTagData("CF_DISPLAYFORM")>
<cfif parentData.Attributes.formStatus EQ "Preparing">
<!--- Associate this child tag with its parent tag --->
<cfassociate basetag="CF_DISPLAYFORM">
</cfif>
<cfif parentData.Attributes.formStatus EQ "Building">
<cfoutput>
<!--- Add and Edit forms output form fields --->
<cfif ListFindNoCase("Add,Edit", parentData.Attributes.formType)>
<tr>
<td>
#Attributes.fieldLabel#
</td>
<td>
<input type="text" name="#Attributes.fieldName#"
<cfif parentData.Attributes.formType EQ "Edit">
value="#parentData.retrieveRecord[Attributes.fieldName][1]#"
</cfif>>
</td>
</tr>
</cfif>
<!--- View and Delete forms output static values --->
<cfif ListFindNoCase("View,Delete", parentData.Attributes.formType)>
<tr>
<td>
#Attributes.fieldLabel#
</td>
<td>
#parentData.retrieveRecord[Attributes.fieldName][1]#
</td>
</tr>
</cfif>
</cfoutput>
</cfif>
</cfif>