Macromedia Coldfusion MX 7 Web Application Construction Kit

As a ColdFusion developer for Orange Whip Studios, you often need to display movie titles. It's easy enough to write a <cfquery> tag that retrieves the title for a particular movie based on its ID, but that can get repetitive if you need to do it on many different pages. Also, you must keep the database's design in mind at all times, instead of just concentrating on the task at hand.

You find yourself wishing you had a function called getFilmTitle() that would return the title of whatever FilmID you passed to it. So, for example, if you wanted to display the title of film number 8, you could just use this:

<cfoutput>#getFilmTitle(8)#</cfoutput>

Well, it turns out that ColdFusion MX makes it remarkably easy to create this function. And you get to create it using the good old <cfquery> tag you already know and love. All you need to do is to surround the <cfquery> with a few extra tags, and voilé!

Let's take a look at what it will take to put this new function into place.

Basic Steps

To create a user-defined function, you follow four basic steps:

1.

Start with a pair of <cffunction> tags. You will insert all the code needed to make the function do its work between the opening and closing <cffunction> tags.

2.

Add a <cfargument> tag for each argument your function will be using as input. If you wish, you can specify some arguments as required and others as optional.

3.

After the <cfargument> tags, add whatever CFML code is needed to make your function do its work. Feel free to use whatever tags and functions you want in this section.

4.

The last step is to use the <cfreturn> tag to return the result of whatever computations or processing your function does. In other words, you use <cfreturn> to specify what your function's output should be.

NOTE

The <cffunction> and related tags discussed in this chapter were introduced in ColdFusion MX. In previous versions of ColdFusion, the only way to create UDFs was with the less powerful <cfscript> tag, which isn't discussed specifically in this book. If you come across a UDF that was created using <cfscript>, you can use it in your code just like the ones discussed in this chapter. For creating new UDFs, we strongly recommend using <cffunction> rather than the older, script-based method.

I could now introduce the syntax and attributes for each of these new tags, but in this case it's easier if we just jump right in so you can see how the tags work together.

Here is the code needed to create the getFilmTitle() user-defined function:

<cffunction name="getFilmTitle"> <cfargument name="filmID" type="numeric" required="Yes"> <!--- Get the film's title ---> <cfquery name="getFilm" datasource="#datasource#" cachedwithin="#createTimespan(0,1,0,0)#"> SELECT MovieTitle FROM Films WHERE FilmID = #ARGUMENTS.filmID# </cfquery> <!--- Return the film's title ---> <cfreturn getFilm.MovieTitle> </cffunction>

As you can see, all three UDF-related tags are used here. First, a pair of <cffunction> tags surrounds the code for the whole function. Next, a <cfargument> tag at the top of the function defines what its input should be. Finally, a <cfreturn> tag at the end returns the function's output. Nearly all UDFs are constructed using this basic pattern.

Everything else between the <cffunction> tags is the actual CFML code that will be executed each time the function is actually used. In this simple example, the only processing that needs to occur to generate the function's output is a simple database query.

As you can see, a special ARGUMENTS scope will contain the value of each argument when the function is actually used. So, if the number 8 is passed to the function's filmID argument, then the value of the ARGUMENTS.filmID variable will be 8. In this case, ARGUMENTS.filmID dynamically creates the SQL that will retrieve the appropriate film title from the database. All that's left to do is to return the title as the function's output, using the <cfreturn> tag. It's that easy.

Using the Function

Once you've written a UDF, you can use it just like any other function. For instance, after the <cffunction> code shown above, you can use the function to display the title for a film, like this:

<cfoutput>#getFilmTitle(8)#</cfoutput>

When ColdFusion encounters the function, it will run the code between the corresponding <cffunction> tags. For the getFilmTitle() function, this means running the <cfquery> tag and returning the film title that gets retrieved from the database.

Of course, you can provide input to the function's arguments dynamically, as with any other function. For instance, if you have a form field named showFilmID, you could use code like the following to display the title corresponding to the ID number that the user provides on the form:

<cfoutput>#getFilmTitle(FORM.showFilmID)#</cfoutput>

You can also use UDFs in <cfset> tags or any other place where you would use a CFML expression. For instance, the following <cfset> tag would create a variable called myFilmInUpperCase, which is the uppercase version of the selected film's title:

<cfset myFilmInUpperCase = uCase(getFilmTitle(FORM.showFilmID))>

UDF Tag Syntax

Now that you've seen a simple example of how the code for a user-defined function is structured, let's take a closer look at the attributes supported by each of the tags involved: <cffunction>,<cfargument>, and <cfreturn>. Tables 22.1, 22.2, and 22.3 show the syntax supported by these three important tags.

Table 22.1. <cffunction> Tag Syntax

ATTRIBUTE

PURPOSE

name

The name of the new function. To actually use the function, you will call it using the name you provide here. The name needs to be a valid CFML identifier, which means it can contain only letters, numbers, and underscores, and its first character must be a letter.

returnType

Optional. You can use this attribute to indicate the type of information that the function will return, such as string, numeric, date, and so on. See Appendix B, "ColdFusion Tag Reference," for the complete list of data types. This attribute is optional, but it helps ensure your UDF runs correctly and should be used.

output

Optional. While most UDFs will simply return a value, a UDF can actually output data as well. This shouldn't be used very often, so in general you should set this value to False. Setting it to False also reduces the white space generated by a call to the UDF.

Table 22.2. <cfargument> Tag Syntax

ATTRIBUTE

PURPOSE

name

The name of the argument. Within the function, a variable will be created in the ARGUMENTS scope that contains the value passed to the argument when the function is actually used.

type

Optional. The data type that should be supplied to the argument when the function is actually used. If you supply a TYPE, ColdFusion will display an error message if someone tries to use the function with the wrong kind of input.

required

Optional. Whether the argument is required for the function to be able to do its work. The default is No (i.e., not required).

default

Optional. For optional arguments (that is, when required="No"), this determines what the value of the argument should be if a value isn't passed to the function when it is actually used.

Table 22.3. <cfreturn> Tag Syntax

RETURN VALUE

PURPOSE

(any expression)

The <cfreturn> tag doesn't have any attributes per se. Instead, you place whatever string, number, date, variable, or other expression you want directly within the <cfreturn> tag.

NOTE

The <cffunction> tag actually supports several more attributes, which are relevant only when the tag is used within the context of a ColdFusion Component. You will learn about the other <cffunction> attributes in Chapter 23.

One of the neatest things about the UDF framework is how easy it is to create functions that have required arguments, optional arguments, or both:

  • If a <cfargument> tag uses required="Yes", the argument must be provided when the function is actually used. If the argument isn't provided at run time, ColdFusion will display an error message.

  • If required="No" and a default attribute have been specified, the function can be called with or without the argument at run time. Go ahead and use the ARGUMENTS scope to refer to the value of the argument. If a value is provided when the function is actually used, that value will be what is present in the ARGUMENTS scope. If not, the default value will be what is in the ARGUMENTS scope.

  • If required="No" and the default attribute have not been specified, the argument is still considered optional. If the function is called without the argument, there will be no corresponding value in the ARGUMENTS scope. You can use the isDefined() function to determine whether the argument was provided at run time. For instance, you would use isDefined("ARGUMENTS.filmID") within a function's code to determine if an optional filmID argument was provided.

You will see optional arguments at work in Listing 22.4.

NOTE

By "run time," I just mean "at the time when the function is actually used." Programmers often use this term to refer to the actual moment of execution for a piece of code.

For instance, if you wanted your function to always return the letter A, you would use:

<cfreturn "A">

If you wanted your function to return the current time, you would use:

<cfreturn timeFormat(now())>

You can use complex expressions as well, like this:

<cfreturn "The current time is: " & timeFormat(now())>

Using Local Variables

To get its work done, a UDF often needs to use <cfset> or other tags that create variables. Most of the time, you don't want these variables to be visible to pages that use the function.

Why Local Variables are Important

Consider the getFilmTitle() function, which you have already seen. The code for this function runs a query named getFilm within the body of the function (that is, between the <cffunction> tags). That query returns only one column, MovieTitle. You probably don't want that query object to continue existing after the function is called. After all, what if someone already has a <cfquery> called getFilm that selects all columns from the Films table, then calls the UDF? That's rightafter the UDF runs, the function's version of the getFilm query (which has only one column) will overwrite the one that the page created before calling the function, and any subsequent code that refers to getFilms probably won't work as expected.

NOTE

Developers often refer to this type of situation as a "variable collision" or a "namespace collision." Whatever the name, it's bad news, because it can lead to unpredictable or surprising results, especially if you are using a UDF that someone else wrote.

What you need is some way to tell ColdFusion that a particular variable should be visible only within the context of the <cffunction> block. Such a variable is called a local variable.

How to Declare a Local Variable

It's easy to create local variables in a UDF. All you need to do is declare the variable as a local variable, using the <cfset> tag and the var keyword, like this:

<cfset var myLocalVariable = "Hello">

The var keyword tells ColdFusion that the variable should cease to exist when the <cffunction> block ends, and that it shouldn't interfere with any other variables elsewhere that have the same name. You almost always want to declare all variables you create within a <cffunction> block as local with the var keyword.

NOTE

You usually don't want a function to have any "side effects" other than producing the correct return value. That way, you know it is always safe to call a function without having to worry about its overwriting any variables you might already have defined.

Here are some rules about local variables:

  • You can declare as many local variables as you want. Just use a separate <cfset> for each one, using the var keyword each time.

  • The <cfset> tags needed to declare local variables must be at the very top of the <cffunction> block, right after any <cfargument> tags. If ColdFusion encounters the var keyword after any line of code that does anything else, it will display an error message.

  • It isn't possible to declare a local variable without giving it a value. That is, <cfset var myLocalVariable> alone isn't valid. There has to be an equals sign (=) in there, with an initial value for the variable. You can always change the value later in the function's code, so just set the variable to an empty string if you're not ready to give it its real value yet.

To make the getFilmTitle() function work correctly so that the getFilm query object is discarded after the function does its work, you need to add a <cfset> tag at the top of the function body, declaring the getFilm variable as a local variable, like so:

<cffunction name="getFilmTitle"> <cfargument name="filmID" type="numeric" required="Yes"> <!--- This variable is for this function's use only ---> <cfset var getFilm = ""> <!--- Get the film's title ---> <cfquery name="getFilm" datasource="#datasource#" cachedwithin="#createTimespan(0,1,0,0)#"> SELECT MovieTitle FROM Films WHERE FilmID = #ARGUMENTS.filmID# </cfquery> <!--- Return the film's title ---> <cfreturn getFilm.MovieTitle> </cffunction>

Because the <cfset> uses the var keyword, ColdFusion now understands that it should discard the getFilm variable after the function executes, and that it shouldn't interfere with any variables elsewhere that have the same name.

NOTE

This <cfset> sets the GetFilm variable to an empty string. It doesn't matter what this initial value is, since the variable will be set to the results of <cfquery> on the next line. In other languages, you might use an initial value of null, but CFML doesn't support the notion of a null value. Every variable always has some kind of value.

Where to Save Your UDFs

Now that you have seen what a completed <cffunction> block looks like, you may be wondering where exactly you are supposed to place it. The answer is simple: you can place your <cffunction> blocks anywhere you want, in any ColdFusion template. Your code can make use of the function anywhere after it encounters the <cffunction> block.

Creating and Using a UDF in the Same File

For instance, Listing 22.1 is a template that uses the <cffunction> block shown earlier to create the getFilmTitle() function, then uses the function to display a list of films (Figure 22.1).

Listing 22.1. FilmList.cfmCreating and using a UDF

<!--- Filename: FilmList.cfm Created by: Nate Weiss (NMW) Please Note Displays a list of films ---> <!--- ****** BEGIN FUNCTION DEFINITIONS ****** ---> <!--- Function: getFilmTitle() ---> <!--- Returns the title of a film, based on FilmID ---> <cffunction name="getFilmTitle"> <!--- One argument: FilmID ---> <cfargument name="filmID" type="numeric" required="Yes"> <!--- This variable is for this function's use only ---> <cfset var getFilm = ""> <!--- Get the film's title ---> <cfquery name="getFilm" datasource="ows" cachedwithin="#createTimespan(0,1,0,0)#"> SELECT MovieTitle FROM Films WHERE FilmID = #Arguments.filmID# </cfquery> <!--- Return the film's title ---> <cfreturn getFilm.MovieTitle> </cffunction> <!--- ****** END FUNCTION DEFINITIONS ****** ---> <!--- Get a list of all FilmIDs ---> <cfquery name="getFilms" datasource="ows"> SELECT FilmID FROM Films </cfquery> <html> <head><title>Film List</title></head> <body> <h3>Here is the current list of Orange Whip Studios films:</h3> <!--- Now it is extremely easy to display a list of film links ---> <cfoutput query="getFilms"> #getFilmTitle(FilmID)#<br> </cfoutput> </body> </html>

Figure 22.1. The getFilmTitle() function makes it easy to display film titles.

Saving UDFs in Separate Files for Easy Reuse

In Listing 22.1, you saw how to create and use a user-defined function, all in the same ColdFusion template. While the function works just fine, it doesn't really make anything any easier. You wouldn't want to have to retype that function every time you wanted to display a movie's title.

Most of the time, you'll want to keep your UDFs in separate files to make them easy to reuse in your various ColdFusion pages. For instance, it would probably be a good idea to create a file named FilmFunctions.cfm that contains the getFilmTitle() function.

Later, as you create other film-related functions, you could put them in the same file. Once you have this file in place, you can simply <cfinclude> it to use the function it contains.

Listing 22.2 shows how to create such a file. As you can see, this is the same <cffunction> block shown in Listing 22.1; here it's simply dropped into its own template.

Listing 22.2. FilmFunctions1.cfmPlacing a UDF in a separate file

<!--- Filename: FilmFunctions1.cfm Created by: Nate Weiss (NMW) Purpose: Creates a library of user-defined functions related to films ---> <!--- Function: GetFilmTitle() ---> <!--- Returns the title of a film, based on FilmID ---> <cffunction name="getFilmTitle"> <!--- One argument: FilmID ---> <cfargument name="filmID" type="numeric" required="Yes"> <!--- This variable is for this function's use only ---> <cfset var getFilm = ""> <!--- Get the film's title ---> <cfquery name="getFilm" datasource="ows" cachedwithin="#createTimespan(0,1,0,0)#"> SELECT MovieTitle FROM Films WHERE FilmID = #ARGUMENTS.filmID# </cfquery> <!--- Return the film's title ---> <cfreturn getFilm.MovieTitle> </cffunction>

Once you have a file like this in place, you just need to include the file via a simple <cfinclude> tag to be able to use the function(s) it contains. For instance, Listing 22.3 is a revised version of the Film List template from Listing 22.1. The results in the browser are exactly the same (Figure 22.2), but the code is much cleaner.

Listing 22.3. FilmList2.cfmUsing UDFs stored in a separate file

<!--- Filename: FilmList2.cfm Created by: Nate Weiss (NMW) Purpose: Displays a list of films ---> <!--- Include the set of film-related user-defined functions ---> <cfinclude template="FilmFunctions1.cfm"> <!--- Get a list of all FilmIDs ---> <cfquery name="getFilms" datasource="ows"> SELECT FilmID FROM Films ORDER BY MovieTitle </cfquery> <html> <head><title>Film List</title></head> <body> <h3>Here is the current list of Orange Whip Studios films:</h3> <!--- Now it is extremely easy to display a list of film links ---> <cfoutput query="getFilms"> #getFilmTitle(FilmID)#<br> </cfoutput> </body> </html>

Figure 22.2. UDFs can encapsulate scripting, HTML, or other lower-level code.

Reusing Code Saves Time and Effort

The <cfinclude> tag at the top of Listing 22.3 allows you to use the getFilmTitle() function later in the same template. You could use this same <cfinclude> tag in any other templates that need to use the function.

In other words, once you have created a user-defined function, it is incredibly easy to reuse it wherever you need. This makes your work easier and more efficient. And if you ever have to make a correction in the getFilmTitle() function, you only need to do so in one place. This makes your project much easier to maintain over time.

TIP

If you want to be able to use the functions in a particular file throughout your entire application, just move the <cfinclude> tag to your Application.cfc file. You're then free to use the functions wherever you wish.

Категории