Welcome!

ColdFusion Authors: Yakov Fain, Pat Romanski, Liz McMillan, Maureen O'Gara, Greg Ness

Related Topics: ColdFusion

ColdFusion: Article

The Art of Creating Functions in ColdFusion

The Art of Creating Functions in ColdFusion

From my very first line of ColdFusion code I was in heaven. There were lots of powerful tags and functions that made Web site creation a dream. What more could I ask for? User-Defined Functions (UDFs), that's what!

I was surprised that a language that was so extensible omitted the ability to create functions. I knew that if CF was going to be all that it could be, I needed to find a way to synthesize a UDF.

Why Bother with UDFs?
Don't get me wrong, ColdFusion has many built-in functions - over 200 at the last count. So why would you ever need to define your own function? Well, take for example two very useful functions, Ucase() and Lcase(). These convert a string to uppercase and lowercase, respectively. But what if you want to convert to proper case? CF doesn't have a Proper() function. This calls for a UDF.

So What Is a UDF?
Before we can create a UDF we need to define what a UDF is: it's a subroutine that can receive one or more parameters and return a meaningful result. Technically, a function or UDF could have no parameters whatsoever (e.g., The Now() function). Regardless of how many parameters a UDF takes, its power is gauged by the meaningful result.

The only "subroutine" that CF has is the custom tag. Can a custom tag act as a function? The answer is yes - the manual even drops hints about this ability. Custom tags meet all the requirements: they can receive any number of parameters and they can return a meaningful result. It's the result part that's a bit tricky.

A Very Basic UDF
Let's pick apart a very basic UDF (see Listing 1). The attributes are the collection of parameters that are passed into the custom tag. For each parameter passed, you'll have one similarly named item in the attributes collection. The example UDF gets a string passed via Attributes.PARAM1. The <CFSET> tag takes the string stored in Attri-butes.PARAM1 and concatenates asterisks to both ends of it.

Next, we need to return the result. We'll need to create a variable that's at the same scope as the calling template. We use the Caller scope to create the variable. When you use the Caller scope it's as if the <CFSET> tag was in the template that called the UDF. Once created, the calling template can use the value in the newly created BasicUDF local variable like any regular local variable. I named the new variable BasicUDF to match the name of my function but this isn't a requirement, just a personal convention.

You might be wondering why the calling template couldn't just read the value of the Result variable. Well, the "calling" template can't even access the Result variable. The Result variable is local to the custom tag and is therefore "out of scope." This is a good thing because it means that variables in the calling template are protected from the custom tag and vice versa.

Calling the UDF is simple if you use <CF_BasicUDF PARAM1="Hello"> in another template. The result would be that a local variable named BasicUDF would have the string "*Hello*" assigned to it. You could then display or manipulate the "BasicUDF" variable as you see fit. Okay, so it's not very useful - but it makes for an easy demonstration. One important note: if you want to retain the value stored in the BasicUDF variable, you need to copy it to another variable before calling <CF_BasicUDF> a second time or the value will be overwritten.

Localizing Variables
I've found that my code is easier to read if I localize my parameters. I do this by copying the values of the attribute parameters into local variables. To do this, I added a line to copy the Attributes. PARAM1 parameter into the local variable "par_Param1" (see Listing 2). You might have noticed that I named the local variable "par_Param1". This lets me know at a glance that I'm dealing with a parameter value without my having to type "Attri-butes" over and over again. I also changed the line that sets the Result variable to use the new par_Param1 variable.

Passing by Value
The way we passed the parameter is called passing by value. That means the value being passed is copied into Attri-butes.PARAM1. Since we're dealing with a copy of the original value, any changes we make do not affect the original value. Depending on what the function does this may be just fine, but if the function needs to modify the original variable, we need to pass it by reference.

Passing by Reference
Passing by reference is when a reference (usually via a pointer ˆ la C/C++) to the original value is passed to the function so that the function can change the original value. In reality, ColdFusion has no provision for passing by reference (as there are no pointers in CF) but we can pass the name of a variable. If we know the name of a variable, then we can write code to modify it. But how can we write code that uses the variable name if we won't know what it is until the template is executed?

Reading the Values
There's a built-in function named "Evaluate" that will resolve the string passed to it for any CF functions or pound(#)-delimited variable names. It then takes that result and evaluates it one last time for a final result. Take for example this line of code:

<CFSET RoomID = 2>
<CFSET RoomName = Evaluate("Classroom#RoomID#")>

The above Evaluate function would resolve "RoomID" to the value "2," which would result in the string "Classroom2". The resulting string would be evaluated and the value stored in the "Classroom2" variable would be assigned to the "RoomName" variable.

Writing the Values
Now we need a way to write to the variable passed by reference. ColdFusion has provided two ways to change a variable when we get the name dynamically.

The first is to use the built-in SetVariable() function. This takes two parameters: first, a string containing the name of the variable to modify; second, the new value to assign. If we had a variable named "VarRef" that had the value "MyVar" and we wanted to set MyVar's value to "Hello" we could use this code:

<CFSET void = SetVariable(VarRef, "Hello")>.

The second way is to use inline interpretation. Inline interpretation is when you use a string - not a string variable but a literal string - on the left side of the equal in a <CFSET> tag. The string gets resolved to a variable name and that variable receives the assignment. So to assign a value to the variable "named" in the MyVar variable, I would use this code:

<CFSET "#MyVar#" = "Hello">.

Listing 3 creates a tag called "CF_ BasicUDF3" that demonstrates how to read and write variables when they're passed by reference. The first <CFSET> tag reads the value of the string parameter. The second <CFSET> tag modifies the value and the third <CFSET> stores the modified value back to the "original" variable.

Checking the Parameters
A well-written function should never rely on the calling template to use the function correctly. Even the best programmers make mistakes like forgetting to pass a parameter or misspelling a parameter name. It's good practice to test if all the required parameters have been passed.

What would happen if I were to call <CF_BasicUDF3> without the StrVarName parameter? I'd get the message "Error resolving parameter ATTRIBUTES.

STRVARNAME." I need to test whether "PARAM1" exists and if not take appropriate action.

CF has two functions that can assist in this endeavor, IsDefined() and ParameterExists(). IsDefined() is the one we want to use; ParameterExists() is provided only for backward compatibility. The IsDefined() function takes one parameter, a string value set to the name of the variable that we want to test to see if it's defined. We can add some code to test if "StrVarName" exists and if it doesn't we can display a less cryptic error message (see Listing 4).

While this works, a better method would be to raise a ColdFusion custom error - but that would be beyond the scope of this article.

Setting Parameter Defaults
At some point you'll most likely want to create a function that has parameters that are optional and/or have parameters with a default value. To achieve this you use the <CFPARAM> tag. This tag has two parameters: the first is "NAME", the name of the parameter to assign the default value. The second parameter is "DEFAULT", the default value to assign. The <CFPARAM> tag tests if the variable specified in the "NAME" parameter is defined. If it isn't, the variable is created and assigned the value specified in the "DEFAULT" parameter. If the variable specified in the "NAME" is defined, the <CFPARAM> tag is ignored. To demonstrate this concept I modified the UDF to take a second parameter. The second parameter, "Symbol", is the character to put around the string. I set the default for "Symbol" to an asterisk (see Listing 5). Now, if I called the tag as follows:

<CF_BasicUDF5 StrVarName="Hello">

I will get "*Hello*" as the return result.

Making the Proper Functions
Pulling all this information together, I created two proper-case UDFs. The first one, <CF_ProperBV>, passes by value (see Listing 6). The second, <CF_ProperBR> passes by reference (see Listing 7). I also created the ProperTest.cfm template (see Listing 8) to demonstrate how the functions are called and how they affect the local variables. Both UDFs are virtually (no pun intended) identical. They both check each character in the passed string, looking for characters that follow a space. Characters that follow a space are capitalized; those that don't are lowercased. The resulting string is returned to the calling template. This is by no means the best way to write a proper case function. It doesn't even take into account "Mc" versus "Mac."

Return on the Investment
It may seem like lot of work to create a function in CF - and, well, it is. But a well-written UDF, like a well-written custom tag, will save hours in the long run. Maybe someday CF will add the ability to create "real" functions. Until that day comes, I hope that you'll benefit from custom tag UDFs as much as I have.

More Stories By John Morgan

John Morgan writes courseware at Blue Star training when he's not busy training programmers, Web developers and database developers. He also speaks at conferences, workshops and the San Diego ColdFusion Users Group, which he hosts.

Comments (0)

Share your thoughts on this story.

Add your comment
You must be signed in to add a comment. Sign-in | Register

In accordance with our Comment Policy, we encourage comments that are on topic, relevant and to-the-point. We will remove comments that include profanity, personal attacks, racial slurs, threats of violence, or other inappropriate material that violates our Terms and Conditions, and will block users who make repeated violations. We ask all readers to expect diversity of opinion and to treat one another with dignity and respect.