facebook google plus twitter
Webucator's Free ColdFusion MX Tutorial

Lesson: ColdFusion Components

Welcome to our free ColdFusion MX tutorial. This tutorial is based on Webucator's Comprehensive ColdFusion Training course.

Lesson Goals

  • To create and use ColdFusion Components.

CFC Basics

ColdFusion Components (CFCs) are very similar to classes in object-oriented languages. They are used to create structured applications and to "blackbox" complicated processing code.

Like all ColdFusion pages, CFCs are simply text files written in CFML; however, they use the extension ".cfc" rather than ".cfm". They can be stored anywhere in the application's directory structure.

Creating CFCs

All code within a CFC should be wrapped in a <cfcomponent> tag, which can contain one or more <cffunction> tags. The <cffunction>s are the methods of the component. The only major difference between functions written in CFCs and standard ColdFusion functions is that a CFC function has an access level, which is assigned in the access attribute of the <cffunction> tag.

Access Level Description
private only accessible from other methods in the same component
package only accessible from components in the same directory
public accessible from any ColdFusion page in the application
remote accessible over the internet and by the Flash Player.

The code below shows an example of a CFC that has similar functionality to the imageflipper custom tag we saw earlier.

Code Sample:

CFCs/Demos/Images.cfc
<cfcomponent>

	<cffunction name="ImageFlipper" access="public" returntype="string">
		<cfargument name="directory" type="string" required="true">
		<cfargument name="extensions" type="string" default="jpg,gif">
		<cfset aryImages = getFileList(arguments.directory,arguments.extensions)>
		<cfparam name="SESSION.picture" default="0">
		<cfset SESSION.picture = SESSION.picture + 1>

		<cfif SESSION.picture GT ArrayLen(aryImages)>
			<cfset SESSION.picture = 1>
		</cfif>
		<cfset imagepath="#arguments.directory#/#aryImages[SESSION.picture]#">
		<cfreturn imagepath>
	</cffunction>
---- C O D E   O M I T T E D ----

	
</cfcomponent>

Storing CFCs

ColdFusion Components can be stored in any of the following locations. ColdFusion will search for them in the order listed.

  1. In the same directory as the calling page - while this is easy for demonstration purposes, it's not very practical, as it means that the CFC is only available within that directory.
  2. In a directory (or subdirectory of a directory) mapped in ColdFusion Administrator under Server Settings -> Mappings.
  3. In a directory (or subdirectory of a directory) specified in ColdFusion Administrator under Extensions -> Custom Tag Paths.
  4. In the web root.

Invoking CFCs

CFC methods are invoked using the <cfinvoke> tag. The tag does one of the following:

  1. instantiates the specified component transiently and then invokes the specified method.
  2. invokes the specified method on a component that has already been instantiated (e.g, with the <cfobject> tag).

Passing Argument Values

CFC method arguments can be passed in any of the following ways:

  1. Using a nested <cfinvokeargument> tag with name and value attributes:
    <cfinvoke 
    	component = "component_name" 
    	method="method_name" 
    	returnvariable="return_variable">
    	<cfinvokeargument name="arg1" value="value">
    	<cfinvokeargument name="arg2" value="value">
    </cfinvoke>
  2. Using attribute-value pairs in the <cfinvoke> tag itself:
    <cfinvoke 
    	component = "component_name" 
    	method="method_name" 
    	returnvariable="return_variable" 
    	arg1="value"
    	arg2="value">
  3. Using the argumentCollection attribute with the value set to a structure storing name-value pairs:
    <cfset args = StructNew()>
    <cfset args.arg1 = "value">
    <cfset args.arg2 = "value">
    <cfinvoke 
       component="component_name" 
       method="method_name" 
       argumentCollection="#args#" 
       returnVariable="return_variable">

The following example shows different ways of invoking the ImageFlipper method of the Images CFC:

Code Sample:

CFCs/Demos/Images.cfm
<html>
<head>
<title>Invoking Component Methods</title>
</head>
<body>
<h1>Invoking Component Methods</h1>
<h2>Example 1: Transient Instantiation</h2>
<cfinvoke component = "Images" method="ImageFlipper" returnvariable="imgpath">
	<cfinvokeargument name="directory" value="Images/Runners">
</cfinvoke>
<cfoutput><img src="#imgpath#"/></cfoutput>

<h2>Example 2: Using attributes rather than cfinvokeargument</h2>
<cfinvoke 
	component = "Images" 
	method="ImageFlipper" 
	returnvariable="imgpath" 
	directory="Images/Runners">

<cfoutput><img src="#imgpath#"/></cfoutput>

<h2>Example 3: Passing the arguments using argumentCollection</h2>
<cfset args = StructNew()>
<cfset args.directory = "Images/Runners">
<cfset args.extensions = "jpg">
<cfinvoke 
   component="Images" 
   method="ImageFlipper" 
   argumentCollection="#args#" 
   returnVariable="imgpath">
<cfoutput><img src="#imgpath#"/></cfoutput>

<h2>Example 4: Separate instantiation and method invocation</h2>
<cfset args = StructNew()>
<cfset args.directory = "Images/Runners">
<cfset args.extensions = "jpg">
<cfobject 
   name="objImage" 
   component="Images2">

<cfloop index="i" from="1" to="7">

<cfinvoke 
   component="#objImage#" 
   method="ImageFlipper" 
   directory="Images/Runners" 
   extensions = "jpg"
   returnVariable="imgpath">

<cfoutput><img src="#imgpath#"/></cfoutput>
</cfloop>

</body>
</html>

Transient Instantiation

The first example above uses transient instantiation. As the component has not yet been instantiated, <cfinvoke> instantiates the component, calls the method and then destroys the component. This is useful when all you need to do is call one method on the object. The argument is passed using <cfinvokeargument>.

The second and third examples are essentially the same, except the second uses attribute-value pairs and the third uses the argumentCollection attribute to pass arguments to the method.

Separate Instantiation and Method Invocation

The fourth example instantiates the component with the <cfobject> tag and then, using a loop, calles the ImageFlipper method seven times. Because the object is being used more than once, it's better to instantiate it separately. It will be destroyed when the page exits.

THIS Scope

You may also notice that the component is built a little differently. Instead of using session variables, it holds a picture variable in the THIS scope that stores the number of the picture showing. The ImageFlipper method changes the value of THIS.picture each time it is called. Variables in the THIS scope are properties of the component instance. That is, if another instance of this component were created, it would have its own picture property.

Writing and Invoking a CFC Method

Duration: 15 to 25 minutes.

In this exercise, you will write a new method called ImageRand in the images component.

  1. Open CFCs/Exercises/Images.cfc in your editor.
  2. Write a function called ImageRand that takes two arguments:
    • directory, which is required
    • extensions, which has the default value of "jpg, gif"
  3. Create an aryImages array of the files specified in the directory. (Hint: use the already-defined getFileList method).
  4. Set a variable called picture that gets a random number between 1 and the number of elements in aryImages.
  5. Set the imagepath variable to contain the path to the randomly chosen image.
  6. Return imagepath.
  7. To test your solution, open CFCs/Exercises/RandImage.cfm in your browser, which calls the ImageRand method you just created. You should see a random image from the Images/Runners directory every time you refresh the page.

Solution:

CFCs/Solutions/Images.cfc
<cfcomponent>

	<cffunction name="ImageFlipper" access="public" returntype="string">
		<cfargument name="directory" type="string" required="true">
		<cfargument name="extensions" type="string" default="jpg,gif">
		<cfset aryImages = getFileList(arguments.directory,arguments.extensions)>
		<cfparam name="SESSION.picture" default="0">
		<cfset SESSION.picture = SESSION.picture + 1>

		<cfif SESSION.picture GT ArrayLen(aryImages)>
			<cfset SESSION.picture = 1>
		</cfif>
		<cfset imagepath="#arguments.directory#/#aryImages[SESSION.picture]#">
		<cfreturn imagepath>
	</cffunction>
	
	<cffunction name="ImageRand" access="public" returntype="string">
		<cfargument name="directory" type="string" required="true">
		<cfargument name="extensions" type="string" default="jpg, gif">
		<cfset aryImages = getFileList(ARGUMENTS.directory,ARGUMENTS.extensions)>
		
		<cfset picture = RandRange(1,ArrayLen(aryImages))>
		
		<cfset imagepath = "#ARGUMENTS.directory#/#aryImages[picture]#">
		
		<cfreturn imagepath>
	
	</cffunction>
	
	<cffunction name="getFileList" access="private" returntype="array">
		<cfargument name="directory" type="string" required="true">
		<cfargument name="extensions" type="string" required="true">
		<cfdirectory directory="#ExpandPath(ARGUMENTS.directory)#"
			action="list" name="getFiles">
		<cfset filenames = ArrayNew(1)>
		<cfloop query="getFiles">
			<cfset FileExt = ListLast(name,".")>
			<cfif ListContainsNoCase(ARGUMENTS.extensions,FileExt) AND type EQ "File">
				<cfset ArrayAppend(filenames,name)>
			</cfif>
		</cfloop>
		<cfreturn filenames>
	</cffunction>
	
</cfcomponent>

Extending ColdFusion Components

ColdFusion components can inherit from other ColdFusion components by means of the extends attribute. The example below shows a ThumbNails component that extends the Images component.

Code Sample:

CFCs/Demos/ThumbNails.cfc
<cfcomponent extends="Images">
	<cffunction name="CreateImageTable" access="public" returntype="string">
		<cfargument name="directory" type="string" required="true">
		<cfargument name="columns" type="numeric" default="2">
		<cfargument name="extensions" type="string" default="jpg,gif">
		<cfset aryImages = getFileList(arguments.directory,arguments.extensions)>
		<cfset rows = Ceiling(ArrayLen(aryImages)/columns)>
		<cfset picture = 1>
		<cfsavecontent variable="ReturnTable">
			<table border="1">
				<cfloop index="i" from="1" to="#rows#">
					<tr>
						<cfloop index="j" from="1" to="#columns#">
							<td>
								<cfoutput><img src="#arguments.directory#/#aryImages[picture]#"></cfoutput>
							</td>
							<cfset picture = picture + 1>
						</cfloop>
					</tr>
				</cfloop>
			</table>
		</cfsavecontent>
		<cfreturn ReturnTable>
	</cffunction>
</cfcomponent>

The ThumbNails component has a single method that displays all the images in the specified directory as a table. The following example makes use of that method and of a method in the parent Images component.

Code Sample:

CFCs/Demos/ThumbNails.cfm
<html>
<head>
<title>Thumbnails</title>
</head>
<body>
<h1>Thumbnails</h1>
<cfobject name="objThumbNail" component="ThumbNails">
   
<cfinvoke component = "#objThumbNail#" method="CreateImageTable" returnvariable="ImgTable">
	<cfinvokeargument name="directory" value="Images/Runners">
	<cfinvokeargument name="columns" value="3">
</cfinvoke>
<cfoutput>#ImgTable#</cfoutput>
<hr />
<cfinvoke component = "#objThumbNail#" method="ImageFlipper" returnvariable="imgpath">
	<cfinvokeargument name="directory" value="Images/Runners">
</cfinvoke>
<cfoutput><img src="#imgpath#"/></cfoutput>
</body>
</html>

Although the examples above aren't very interesting in terms of functionality, they should give you the idea of how CFCs can be extended.

Application.cfc

There is a special reserved Application.cfc component that works much like the Application.cfm and OnRequestEnd.cfm files, but uses an object-oriented approach, which allows it to be extended in a way that the Application.cfm file cannot.

Like Application.cfm, the Application.cfc component is called before each page request. Its THIS scope is used to set the same application properties that can be set with <cfapplication>'s attributes:

Variable Description
name Name of application.
applicationTimeout Lifespan of application variables. Default is set in Administrator.
clientManagement Enable client variables. Default is set in Administrator.
clientStorage Where to store client variables. Default is set in Administrator.
loginStorage Where to store login variables (in Cookies or Session). Default is "Cookie".
sessionManagement Enable session management. Default is "No".
sessionTimeout Lifespan of session variables. Default is set in Administrator.
setClientCookies Enable cookies. Default is "Yes".
setDomainCookies Sets CFID and CFTOKEN for a domain. Default is "No".
scriptProtect Protect variables from cross-site scripting attacks. Default is set in Administrator.

The Application.cfc component has eight pre-named methods, none of which is required. They are described below.

Application.cfc Methods

onApplicationStart

The onApplicationStart method is invoked when the first page of the application is requested. It can be used to:

  • test database connections.
  • verify the existence of key files.
  • test the mail server.
  • set application variables.
  • log the time the application started.

onApplicationEnd

The onApplicationEnd method is invoked when the application times out. It can be used to log the time the application ended.

onRequestStart

The onRequestStart method is invoked at the start of every page request. It can be used to:

  • authenticate the user.
  • output a standard header.
  • include common function libraries.

onRequest

The onRequest method is invoked immediately after the onRequestStart method. It takes one argument: the path to the target page. The method can be used to:

  • filter content.
  • output a standard header.
  • output a standard footer.

onRequestEnd

The onRequestEnd method is invoked at the end of every page request. It can be used to output a standard footer.

onSessionStart

The onSessionStart method is invoked when a user's session starts. It can be used to:

  • set session variables.
  • log the time the session started.

onSessionEnd

The onSessionEnd method is invoked when the session ends. It can be used to log the time the session ended.

onError

The onError method is invoked when an exception is not caught by a try/catch block. It can be used to:

  • Log errors.
  • Display friendly error messages.
  • Email the site administrator.

We cover the onError method in greater detail in the Handling Errors.

Application.cfc Benefits

Why do we need Application.cfc? What was wrong with Application.cfm? Application.cfm certainly is useful, but it has some major shortcomings, which Application.cfc addresses.

Application.cfm Shortcomings

Only one Application.cfm page per request

Each time a page runs, ColdFusion looks for an Application.cfm page in the current directory and then in each parent directory up to the web root until it finds one. As soon as it finds an Application.cfm page, it runs it and stops looking. The problem with this system is that it makes it difficult to treat any single directory differently from the rest of the application. To do so, you have to create an entirely new Application.cfm file for that directory, even though much of the code will be the same as in the other Application.cfm files in the application. If you decide to make an application-wide change, you will have to make the change in each Application.cfm file.

ColdFusion actually searches for Application.cfc files in the same way it searches for Application.cfm files and it stops as soon as it finds one. The big difference is that one Application.cfc can extend another. This means that we don't have to repeat all the same code every time we want a slight change in functionality.

No easy way to log sessions

Application.cfm does not provide any mechanism for flagging the start and end of sessions. Application.cfc's onSessionStart and onSessionEnd methods are invoked when a session starts and ends, so the developer can easily log information about the visit.

Application code runs every time a page is requested

All the code in Application.cfm runs every time a page is requested. This makes it a less than ideal place for performing expensive tests like verifying a database connection. We can get around this by writing code to check if the database has already been verified:

<cfif NOT isDefined("APPLICATION.DatabaseVerified")> <cftry> <cfquery name="VerifyDB" datasource="Animals"> SELECT AnimalType FROM tblAnimals </cfquery> <cflock timeout="5" throwontimeout="No" type="EXCLUSIVE" scope="SESSION"> <cfset APPLICATION.DatabaseVerified = true> </cflock> <cfcatch type="database"> <cflog file="Animals" type="error" text="Message: #cfcatch.message# Detail: #cfcatch.detail# Native Error: #cfcatch.NativeErrorCode#"> </cfcatch> </cftry> </cfif>

Application.cfc's onApplicationStart method makes this simpler as the method is only invoked one time: when the application is started. This means we don't have to check to see if the connection has already been verified and we don't have to lock application variables. It also allows us to take more serious action, like stopping the application from starting. The method might be written like this:

<cffunction name="onApplicationStart" output="true"> <cftry> <cfquery name="VerifyDB" datasource="Animals"> SELECT AnimalType FROM tblAnimals </cfquery> <cflog file="Animals" type="Information" text="Application Started"> <cfcatch type="database"> <cfoutput>Datasource "Animals" not available.</cfoutput> <cflog file="Animals" type="error" text="Message: #cfcatch.message# Detail: #cfcatch.detail# Native Error: #cfcatch.NativeErrorCode#"> <cfreturn false> </cfcatch> </cftry> </cffunction>

Extending Application.cfc - An Example

Imagine a site for dogs, cats and mice. All certified animals (dogs, cats and mice) can access the main part of the site, but there is a part of the site reserved for cats and another part reserved for dogs. The directory structure looks like this:

In this demo, we will indicate who the user is by passing "who=dog" or "who=cat" along the querystring. We want the application to act as follows:

  • Animals/index.cfm and other pages under the Animals directory, but not under the Cats or Dogs directories.
    • If URL.who is undefined or does not equal "dog", "cat" or "mouse":
    • If URL.who equals "dog", "cat" or "mouse":
  • Animals/Dogs/index.cfm and other pages under the Dogs directory.
    • If URL.who is undefined or does not equal "dog":
    • If URL.who equals "dog" :
  • Animals/Cats/index.cfm and other pages under the Cats directory.
    • If URL.who is undefined or does not equal "cat":
    • If URL.who equals "cat" :

The index.cfm pages all look the same, except for the words "animal", "dog", and "cat". The code for Animals/index.cfm is shown below, followed by the code for its Application.cfc file.

Code Sample:

CFCs/Demos/Animals/index.cfm
<html>
<head>
<title>You're an animal!</title>
</head>
<body>
You're an animal!
</body>
</html>

Code Sample:

CFCs/Demos/Animals/Application.cfc
<cfcomponent>

<cfset THIS.name="Animals">
<cfset THIS.sessionManagement="true">
<cfset THIS.clientManagement="true">
<cfset THIS.setClientCookies="true">
<cfset THIS.applicationTimeout=CreateTimeSpan(2,0,0,0)>
<cfset THIS.sessionTimeout=CreateTimeSpan(0,0,20,0)>

<cffunction name="onApplicationStart">
	<!--- POSSIBLE USES:
		Test database connection
		Test mail server
		Set application variables (do not have to lock code)
		Log the start of the application
	--->
	<cfset APPLICATION.animals = "dog,cat,mouse">
</cffunction>

<cffunction name="onApplicationEnd">
	<!--- POSSIBLE USES:
		Log the end of the application
	--->
</cffunction>

<cffunction name="onRequestStart">
	<!--- POSSIBLE USES:
		Authentication code
		Output header
	--->
	<cfif NOT isDefined("URL.who") OR NOT ListContains(APPLICATION.animals,URL.who)>
		You must be a certified animal to visit this page.
		<cfabort>
	</cfif>
</cffunction>

<cffunction name="onRequest">
	<!--- POSSIBLE USES:
		Filter content
		Output header
		Output footer
	--->
	<cfargument name = "targetPage" type="String" required="true"/>
	<cfsavecontent variable="content">
		<cfinclude template="#ARGUMENTS.targetPage#">
	</cfsavecontent>
	<cfoutput> #replace(content, "badword", "%&!@", "all")# </cfoutput>
</cffunction>

<cffunction name="onRequestEnd">
	<cfargument type="String" name = "targetTemplate" required="true"/>
	<!--- POSSIBLE USES:
		Output footer
	--->
	<hr/>
	&copy; Animals Incorporated
</cffunction>

<cffunction name="onSessionStart">
	<!--- POSSIBLE USES:
		Set session variables
		Log the start of the session
	--->
	<cflock timeout="5" throwontimeout="No" type="EXCLUSIVE" scope="SESSION">
		<cfset APPLICATION.sessions = APPLICATION.sessions + 1>
	</cflock>
	<cflock timeout="5" throwontimeout="No" type="EXCLUSIVE" scope="SESSION">
		<cfset APPLICATION.currentSessions = APPLICATION.currentSessions + 1>
	</cflock>
</cffunction>

<cffunction name="onSessionEnd">
	<cfargument name = "SessionScope" required="true"/>
	<!--- POSSIBLE USES:
		Log the end of the session
	--->
	<cflock timeout="5" throwontimeout="No" type="EXCLUSIVE" scope="SESSION">
		<cfset APPLICATION.currentSessions = APPLICATION.currentSessions - 1>
	</cflock>
</cffunction>

<cffunction name="onError">
	<cfargument name="Exception" required="true"/>
	<cfargument type="String" name = "EventName" required="true"/>
	<!---  POSSIBLE USES:
		Log errors
		Display error
		Email site administrator
	--->
</cffunction>
</cfcomponent>

This Application.cfc file includes code to set the THIS scope variables and it also includes all eight reserved methods. For our purposes, the interesting methods to look at are onApplicationStart, onRequestStart, and onRequestEnd.

onApplicationStart sets APPLICATION.animals, which determines which animals are allowed on the site.

onRequestStart checks to make sure that the visitor is an allowed animal.

onRequestEnd outputs a standard footer.

Now let's look at Dogs/Application.cfc.

Code Sample:

CFCs/Demos/Animals/Dogs/Application.cfc
<cfcomponent extends="Animals.Application" output="yes">
	<cffunction name="onRequestStart" output="yes">
		<cfif NOT isDefined("URL.who") OR URL.who NEQ "dog">
			You must be a dog to visit this page.
			<cfabort>
		</cfif>
	</cffunction>
</cfcomponent>

Things to notice:

  • The component extends Animals.Application. (For this to work, you'll need to create a mapping in Administrator to the CFCs/Demos/Animals.) This means that all the code in Animals/Application.cfc will run unless something is overridden.
  • Only the onRequestStart method, which authenticates the user, is overridden.

CFCs/Demos/Animals/Cats/Application.cfc works exactly the same way.

Extending Application.cfc

Duration: 20 to 30 minutes.

In this exercise, you will create an Application.cfc file that outputs a header on every page. In a subdirectory, you will create another Application.cfc file that changes the header.

  1. Open CFCs/Exercises/Animals/Application.cfc and review the onRequestStart method. You will see that it now outputs a simple navigation bar.
  2. Write code so that pages in the Dogs, Cats, and Mice directories get their own navigation bars, which each have a single link back to Animals/index.cfm.

Change the footer on pages in the Mice directory to include the text "Mice are cool!".

Solution:

CFCs/Solutions/Animals/Dogs/Application.cfc
<cfcomponent extends="Animals.Application" output="yes">
	<cffunction name="onRequestStart" output="yes">
		<cfif NOT isDefined("URL.who") OR URL.who NEQ "dog">
			You must be a dog to visit this page.
			<cfabort>
		<cfelse>
			<cfoutput>
			<div>
				<a href="../index.cfm?who=#URL.who#">Animal Home</a>
			</div>
			</cfoutput>
		</cfif>
	</cffunction>
</cfcomponent>

Challenge Solution:

CFCs/Solutions/Animals/Mice/Application.cfc
<cfcomponent extends="Animals.Application" output="yes">
	<cffunction name="onRequestStart" output="yes">
		<cfif NOT isDefined("URL.who") OR URL.who NEQ "mouse">
			You must be a mouse to visit this page.
			<cfabort>
		<cfelse>
			<cfoutput>
			<div>
				<a href="../index.cfm?who=#URL.who#">Animal Home</a>
			</div>
			</cfoutput>
		</cfif>
	</cffunction>
	<cffunction name="onRequestEnd">
		<cfargument type="String" name = "targetTemplate" required="true"/>
		<hr/>
		&copy; Animals Incorporated. Mice are cool!
	</cffunction>
</cfcomponent>