Creating a Complete Application
Now that you've created add, modify, and delete templates, let's put them all together and create a finished applicationand this time, one that is constructed properly, using a CFC for database access.The following templates are a combination of all you have learned in this and previous chapters.Chapter 11, "The Basics of Structured Development."
Listing 14.15. movies.cfcMovie Database Access
movies.cfc contains six methods, list lists all movies, get gets a specific movie, geTRatings returns a list of all possible ratings, and add, update, and delete add, update, and delete movies respectively.
<!---
Name: movies.cfc
Author: Ben Forta (ben@forta.com)
Description: Movie database access component
Created: 12/21/04
--->
<cfcomponent hint="OWS movie database access">
<!--- Set the datsources --->
<cfset ds="ows">
<!--- Get movie list --->
<cffunction name="list"
returntype="query"
hint="List all movies">
<cfquery datasource="#ds#"
name="movies">
SELECT FilmID, MovieTitle
FROM Films
ORDER BY MovieTitle
</cfquery>
<cfreturn movies>
</cffunction>
<!--- Get details for a movie --->
<cffunction name="get"
returntype="query"
hint="Get movie details">
<cfargument name="FilmID"
type="numeric"
required="yes"
hint="Movie ID">
<cfquery datasource="#ds#"
name="movie">
SELECT FilmID, MovieTitle,
PitchText, AmountBudgeted,
RatingID, Summary,
ImageName, DateInTheaters
FROM Films
WHERE FilmID=#ARGUMENTS.FilmID#
</cfquery>
<cfreturn movie>
</cffunction>
<!--- Add a movie --->
<cffunction name="add"
returntype="boolean"
hint="Add a movie">
<!--- Method arguments --->
<cfargument name="MovieTitle"
type="string"
required="yes"
hint="Movie title">
<cfargument name="PitchText"
type="string"
required="yes"
hint="Movie tag line">
<cfargument name="AmountBudgeted"
type="numeric"
required="yes"
hint="Projected movie budget">
<cfargument name="RatingID"
type="numeric"
required="yes"
hint="Movie rating ID">
<cfargument name="Summary"
type="string"
required="yes"
hint="Movie summary">
<cfargument name="DateInTheaters"
type="date"
required="yes"
hint="Movie release date">
<cfargument name="ImageName"
type="string"
required="no"
default="
hint="Movie image file name">
<!--- Insert movie --->
<cfquery datasource="#ds#">
INSERT INTO Films(MovieTitle,
PitchText,
AmountBudgeted,
RatingID,
Summary,
ImageName,
DateInTheaters)
VALUES('#Trim(ARGUMENTS.MovieTitle)#',
'#Trim(ARGUMENTS.PitchText)#',
#ARGUMENTS.AmountBudgeted#,
#ARGUMENTS.RatingID#,
'#Trim(ARGUMENTS.Summary)#',
'#Trim(FORM.ImageName)#',
#CreateODBCDate(ARGUMENTS.DateInTheaters)#)
</cfquery>
<cfreturn true>
</cffunction>
<!--- Update a movie --->
<cffunction name="update"
returntype="boolean"
hint="Update a movie">
<!--- Method arguments --->
<cfargument name="FilmID"
type="numeric"
required="yes"
hint="Movie ID">
<cfargument name="MovieTitle"
type="string"
required="yes"
hint="Movie title">
<cfargument name="PitchText"
type="string"
required="yes"
hint="Movie tag line">
<cfargument name="AmountBudgeted"
type="numeric"
required="yes"
hint="Projected movie budget">
<cfargument name="RatingID"
type="numeric"
required="yes"
hint="Movie rating ID">
<cfargument name="Summary"
type="string"
required="yes"
hint="Movie summary">
<cfargument name="DateInTheaters"
type="date"
required="yes"
hint="Movie release date">
<cfargument name="ImageName"
type="string"
required="no"
default="
hint="Movie image file name">
<!--- Update movie --->
<cfquery datasource="#ds#">
UPDATE Films
SET MovieTitle='#Trim(ARGUMENTS.MovieTitle)#',
PitchText='#Trim(ARGUMENTS.PitchText)#',
AmountBudgeted=#ARGUMENTS.AmountBudgeted#,
RatingID=#ARGUMENTS.RatingID#,
Summary='#Trim(ARGUMENTS.Summary)#',
ImageName='#Trim(ARGUMENTS.ImageName)#',
DateInTheaters=#CreateODBCDate(ARGUMENTS.DateInTheaters)#
WHERE FilmID=#ARGUMENTS.FilmID#
</cfquery>
<cfreturn true>
</cffunction>
<!--- Delete a movie --->
<cffunction name="delete"
returntype="boolean"
hint="Delete a movie">
<cfargument name="FilmID"
type="numeric"
required="yes"
hint="Movie ID">
<cfquery datasource="#ds#">
DELETE FROM Films
WHERE FilmID=#ARGUMENTS.FilmID#
</cfquery>
<cfreturn true>
</cffunction>
<!--- Get movie ratings --->
<cffunction name="getRatings"
returntype="query"
hint="Get movie ratings list">
<!--- Get ratings --->
<cfquery datasource="#ds#"
name="ratings">
SELECT RatingID, Rating
FROM FilmsRatings
ORDER BY RatingID
</cfquery>
<cfreturn ratings>
</cffunction>
</cfcomponent>
NOTE
Notice that the value passed to all datasource attributes in Listing 14.15 is a variable (which is set at the top of the page) so as to not hard-code the value multiple times.Listing 14.16 is the main movie maintenance page. It displays the movies returned from the ColdFusion Component and provides links to edit and delete them (using the data drill-down techniques discussed in previous chapters); it also has a link to add a new movie. The administration page is shown in Figure 14.5.Listing 14.16. movies.cfmMovie List Maintenance Page
<!---
Name: movies.cfm
Author: Ben Forta (ben@forta.com)
Description: Movie maintenance application
Created: 12/21/04
--->
<!--- Get all movies --->
<cfinvoke component="movies"
method="list"
returnvariable="movies">
<!--- Page header --->
<cfinclude template="header.cfm">
<table align="center" bgcolor="orange">
<!--- Loop through movies --->
<cfoutput query="movies">
<tr>
<!--- Movie name --->
<td><strong>#MovieTitle#</strong></td>
<!--- Edit link --->
<td>
[<a href="movie_edit.cfm?FilmID=#FilmID#">Edit</a>]
</td>
<!--- Delete link --->
<td>
[<a href="movie_delete.cfm?FilmID=#FilmID#">Delete</a>]
</td>
</tr>
</cfoutput>
<tr>
<td></td>
<!--- Add movie link --->
<td colspan="2" align="center">
[<a href="movie_edit.cfm">Add</a>]
</td>
</tr>
</table>
<!--- Page footer --->
<cfinclude template="footer.cfm">
Figure 14.5. The movie administration page is used to add, edit, and delete movies.
Chapter 12, "ColdFusion Forms."Listing 14.17 is essentially the same reusable add and update form you created earlier, but with another useful shortcut.
Listing 14.17. movie_edit.cfmMovie Add and Update Form
There are only three changes in Listing 14.17. All <cfquery> tags have been removed and replaced by <cfinvoke> tags (obtaining the data from movies.cfc). The action has been changed to point to a new filemovie_process.cfm. In addition, look at the RatingID field. It uses a new tag named <cfselect>. This tag, which can be used only within <cfform> and </cfform> tags, simplifies the creation of dynamic data-driven <select> controls. The code
<!---
Name: movie_edit.cfm
Author: Ben Forta (ben@forta.com)
Description: Dual purpose movie edit form
Created: 12/21/04
--->
<!--- Check that FilmID was provided --->
<!--- If yes, edit, else add --->
<cfset EditMode=IsDefined("URL.FilmID")>
<!--- If edit mode then get row to edit --->
<cfif EditMode>
<!--- Get the film record --->
<cfinvoke component="movies"
method="get"
filmid="#URL.FilmID#"
returnvariable="film">
<!--- Save to variables --->
<cfset MovieTitle=Trim(film.MovieTitle)>
<cfset PitchText=Trim(film.PitchText)>
<cfset AmountBudgeted=Int(film.AmountBudgeted)>
<cfset RatingID=film.RatingID>
<cfset Summary=Trim(film.Summary)>
<cfset ImageName=Trim(film.ImageName)>
<cfset DateInTheaters=DateFormat(film.DateInTheaters, "MM/DD/YYYY")>
<!--- Form text --->
<cfset FormTitle="Update a Movie">
<cfset ButtonText="Update">
<cfelse>
<!--- Save to variables --->
<cfset MovieTitle=">
<cfset PitchText=">
<cfset AmountBudgeted=">
<cfset RatingID=">
<cfset Summary=">
<cfset ImageName=">
<cfset DateInTheaters=">
<!--- Form text --->
<cfset FormTitle="Add a Movie">
<cfset ButtonText="Insert">
</cfif>
<!--- Get ratings --->
<cfinvoke component="movies"
method="getRatings"
returnvariable="ratings">
<!--- Page header --->
<cfinclude template="header.cfm">
<!--- Add/update movie form --->
<cfform action="movie_process.cfm">
<cfif EditMode>
<!--- Embed primary key as a hidden field --->
<cfoutput>
<input type="hidden" name="FilmID" value="#Film.FilmID#">
</cfoutput>
</cfif>
<table align="center" bgcolor="orange">
<tr>
<th colspan="2">
<cfoutput>
<font size="+1">#FormTitle#</font>
</cfoutput>
</th>
</tr>
<tr>
<td>
Movie:
</td>
<td>
<cfinput type="Text"
name="MovieTitle"
value="#MovieTitle#"
message="MOVIE TITLE is required!"
required="Yes"
validateAt="onSubmit,onServer"
size="50"
maxlength="100">
</td>
</tr>
<tr>
<td>
Tag line:
</td>
<td>
<cfinput type="Text"
name="PitchText"
value="#PitchText#"
message="TAG LINE is required!"
required="Yes"
validateAt="onSubmit,onServer"
size="50"
maxlength="100">
</td>
</tr>
<tr>
<td>
Rating:
</td>
<td>
<!--- Ratings list --->
<cfselect name="RatingID"
query="ratings"
value="RatingID"
display="Rating"
selected="#VARIABLES.RatingID#">
</cfselect>
</td>
</tr>
<tr>
<td>
Summary:
</td>
<td>
<cfoutput>
<textarea name="summary"
cols="40"
rows="5"
wrap="virtual">#Summary#</textarea>
</cfoutput>
</td>
</tr>
<tr>
<td>
Budget:
</td>
<td>
<cfinput type="Text"
name="AmountBudgeted"
value="#AmountBudgeted#"
message="BUDGET must be a valid numeric amount!"
required="NO"
validate="integer"
validateAt="onSubmit,onServer"
size="10"
maxlength="10">
</td>
</tr>
<tr>
<td>
Release Date:
</td>
<td>
<cfinput type="Text"
name="DateInTheaters"
value="#DateInTheaters#"
message="RELEASE DATE must be a valid date!"
required="NO"
validate="date"
validateAt="onSubmit,onServer"
size="10"
maxlength="10">
</td>
</tr>
<tr>
<td>
Image File:
</td>
<td>
<cfinput type="Text"
name="ImageName"
value="#ImageName#"
required="NO"
size="20"
maxlength="50">
</td>
</tr>
<tr>
<td colspan="2" align="center">
<cfoutput>
<input type="submit" value="#ButtonText#">
</cfoutput>
</td>
</tr>
</table>
</cfform>
<!--- Page footer --->
<cfinclude template="footer.cfm">
is functionally the same as
<cfselect name="RatingID"
query="ratings"
value="RatingID"
display="Rating"
selected="#VARIABLES.RatingID#">
</cfselect>
Obviously, using <cfselect> is much cleaner and simpler. It creates a <select> control named RatingID that is populated with the ratings query, using the RatingID column as the value and displaying the Rating column. Whatever value is in the variable RatingID will be used to pre-select the selected option in the control.Listings 14.18 calls the appropriate movies.cfc methods to add or update movies.
<select name="RatingID">
<cfoutput query="ratings">
<option value="#RatingID#"
<cfif ratings.RatingID IS VARIABLES.RatingID>
selected
</cfif>>
#Rating#</option>
</cfoutput>
</select>
Listing 14.18. movie_process.cfmMovie Insert and Update
Listing 14.18 uses a <cfif> statement to determine which method to invoke (add or update). It then uses a <cfinvoke> tag set containing a <cfinvokeargument> for each argument.
<!---
Name: movie_process.cfm
Author: Ben Forta (ben@forta.com)
Description: Process edit page
Created: 12/21/04
--->
<!--- Edit or update? --->
<cfif IsDefined("FORM.FilmID")>
<cfset method="update">
<cfelse>
<cfset method="add">
</cfif>
<!--- Do it --->
<cfinvoke component="movies"
method="#method#">
<!--- FilmID only if update method --->
<cfif IsDefined("FORM.FilmID")>
<cfinvokeargument name="FilmID"
value="#FORM.FilmID#">
</cfif>
<cfinvokeargument name="MovieTitle"
value="#Trim(FORM.MovieTitle)#">
<cfinvokeargument name="PitchText"
value="#Trim(FORM.PitchText)#">
<cfinvokeargument name="AmountBudgeted"
value="#Int(FORM.AmountBudgeted)#">
<cfinvokeargument name="RatingID"
value="#Int(FORM.RatingID)#">
<cfinvokeargument name="Summary"
value="#Trim(FORM.Summary)#">
<cfinvokeargument name="ImageName"
value="#Trim(FORM.ImageName)#">
<cfinvokeargument name="DateInTheaters"
value="#DateFormat(FORM.DateInTheaters)#">
</cfinvoke>
<!--- When done go back to movie list --->
<cflocation url="movies.cfm">
TIP
<cfinvokeargument> tags may be included conditionally, as is the case here for the FilmID argument. This is an advantage of <cfinvokeargument> syntax over name=value syntax.Listing 14.19 simply invokes the CFC delete method to delete a movie. The code in listings 14.18 and 14.19 provide no user feedback at all. Instead, they return to the administration screen using the <cflocation> tag as soon as they finish processing the database changes. <cflocation> is used to switch from the current template being processed to any other URL, including another ColdFusion template. The following sample code instructs ColdFusion to switch to the movies.cfm template:This way, the updated movie list is displayed, ready for further processing, as soon as any change is completed.
<cflocation URL="movies.cfm">
Listing 14.19. movie_delete.cfmMovie Delete Processing
And there you have it: a complete n-tier application featuring data display, edit and delete using data-drill down, and reusable data-driven add and edit formsall in under 300 lines of code, including comments. Extremely powerful, and not complicated at all.
<!---
Name: movie_delete.cfm
Author: Ben Forta (ben@forta.com)
Description: Delete a movie
Created: 12/21/04
--->
<!--- Check that FilmID was provided --->
<cfif NOT IsDefined("FilmID")>
<h2>You did not specify the FilmID</h2>
<cfabort>
</cfif>
<!--- Delete a movie --->
<cfinvoke component="movies"
method="delete"
filmid="#URL.FilmID#">
<!--- When done go back to movie list --->
<cflocation url="movies.cfm">
