User-Defined Functions (UDFs)


Macromedia added the concept of UDFs in ColdFusion 5.0. This was done after numerous requests from developers who wanted to create their own functions in a way that was similar to how developers created functions in other languages. With ColdFusion MX, Macromedia has added a new functionality to UDFs to make them easier to work with and more robust. They are also allowing developers to create UDFs in CFML instead of in only CFScript.

Before we move into how to create UDFs, let's go over a few reasons why we would want to use UDFs. As a developer, you often find yourself creating ColdFusion scripts that basically do the same thing over and over again. Often, these scripts have at their core simple functions or algorithms that you usually just cut and paste into your code, because creating an actual ColdFusion tag doesn't seem to be worth the time.

UDFs enable you to create ColdFusion functions that wrap or contain the frequently used snippets of code. You then can use them exactly as you would any other ColdFusion function. For instance, you can use them in tag attributes, between pound (#) signs in output, and in CFScript code. This can save you a huge amount of time and enable you to create more robust applications that require that you change only one file when changing the nature of a function. This is better than searching through countless templates that might have some custom snippet.

The uses of UDFs include the following:

  • Data manipulation routines, such as a function to reverse an array

  • String and date/time routines, such as a function to determine whether a string is a valid Internet Protocol (IP) address

  • Mathematical calculation routines, such as standard trigonometric and statistical operations or calculating loan amortization

  • Routines that call functions externally (for example, using COM or CORBA), including routines to determine the space available on a Windows File System drive

Using UDFs

You typically define a UDF on your ColdFusion page or on a page that you include. You can also define the function on one page and put it in a scope that is shared with the page that calls it. In addition, you can put commonly used functions on a single CFML page and include the page in your Application.cfm page.

You call a UDF as you would a normal ColdFusion function. One example of a UDF is a current function that calculates the current in a circuit as amperes, based on impedance and resistance (Ohm's law). You might use the function like this:

 <cfoutput>  Current in Amps: #Current(form.resistance, form.impedance)#  </cfoutput> 

You use the function statement to define the function in CFScript, or you can use the CFFUNCTION tag, which we cover in more detail in Chapter 6, "ColdFusion Components."

Using CFScript to create UDFs has some distinct advantages when compared to using CFML:

  • The function definition syntax is more familiar to developers who use JavaScript.

  • CFScript is more efficient for writing business logic, such as expressions and conditional operations.

On the other hand, CFScript has some disadvantages as well:

  • CFScript function definitions cannot include CFML tags.

  • CFScript's exception handling currently is not as robust and feature-rich as that of CFML tags.

So, with the information in the bulleted lists in mind, let's look at some simple examples of a CFScript function:

 <cfscript>  function nameofthefunction(arg1, arg2, arg3, …)  {           variable statements;  cfscript statements;  return statements;  }  </cfscript> 

UDFs in CFScript must begin with the Function statement, although UDFs created with CFFUNCTION begin with the tag attribute name="methodname". You need to be careful when you name your UDF because UDF names cannot begin with the letters "CF" and can contain only numbers, letters, and underscores. In addition, when you are creating the name of your UDFs, you should try to create a name that is unique to the UDFs on your machine and in your application. You should do this because a UDF that has the same name as an existing CMFL function or UDF throws an error.

UDFs can also accept any numbers of parameters or have any number or variables. (For our purposes, parameters are just like UDF internal variables, but they are passed to the UDF.) Parameters can be required or optional and follow the same naming restrictions as UDFs themselves. In the example we are going to create next, we pass two required parameters to our UDF: RESISTANCE and IMPEDANCE.

Now let's create a simple UDF example. Listing 5.1 shows you such an example.

Listing 5.1 current.cfm
 <cfscript>  function current(E, R)  {    //Ohm's law    //I = Current in amperes    //E = Voltage in volts    //R = Resistance in ohms    if(NOT isNumeric(E) OR NOT isNumeric(R))    {      writeoutput("values for voltage or resistance must be numeric!!<br>");      return false;    }    return E / R;  }  </cfscript>  <cfset volts = 100 >  <cfset resistance = 3>  <cfset curamps =  current(volts, resistance)>  <cfoutput>#curamps#: AMPS</cfoutput> 

Let's break down this UDF. As we can see from the second line of this UDF, we have created a function called Current(), which accepts two parameters, E and R. Next we do some simple exception handling to make sure that the values passed are numbers.

We then use the reserved CFScript statement return. The return statement is required and determines what values should be returned on execution of the function. It's useful to note that UDFs can return any ColdFusion data type, including arrays, structures, and queries.

As you can see with the next lines of CFML, we set the values for volts and resistance and then called our UDF. When calling UDFs, you should give some thought to how you are going to call them. You can define your UDF in the same page (as we have done here) or you can include it as a file using CFINCLUDE. In general, you should use CFINCLUDE to access your UDFs unless you are using the same UDFs over and over again. Then you might want to use Application.cfm to load the UDF for every template.

Before we discuss UDFs further, let's take a look at developing the same UDF using CFFUNCTION and CFML tags. The general syntax for CFFUNCTION is as follows:

 <cffunction       name = "methodName"       returnType = "dataType"       roles = "securityRoles"       access = "methodAccess"       output = "yes" or "no"       exceptions = "exception1, exception2, ..."  > 

To generate the same functionality as the current.cfm UDF, we can do what is shown in Listing 5.2.

Listing 5.2 current1.cfm
 <cffunction name="current1" output="yes">  <cfargument name="volts">  <cfargument name="resistance">  <cfreturn volts/resistance>  </cffunction>  <cfset volts = 100 >  <cfset resistance = 3>  <cfset curamps =  current1(volts, resistance)>  <cfoutput>#curamps#: AMPS</cfoutput> 

CFFUNCTION enables you to use any CFML tag or CFScript construct inside the CFFUNCTION tags. Furthermore, it provides the developer with robust security and roles-based access to the function you create. In this example, we set the parameters using the CFARGUMENT tag. The CFARGUMENT tag acts just like the VAR statement in CFScript when you are creating UDFs with CFScript.

Then we use the CFRETURN tag to return the value of volts divided by resistance. Then we reference the function in exactly the same way.

It's very simple to create a UDF in CFScript as well as in CFML, so why would you want to do it using CFML and CFFUNCTION? Some reasons include the following:

  • Most developers find building functions in CFML easier than in CFScript if they do not already know JavaScript or EMACS.

  • You can use any CFML tag inside a CFFUNCTION tag and thus have greater flexibility in creating UDFs.

  • You can use CFScript inside a function definition.

  • You can control the access method to your CFFUNCTION. In addition, the specific security roles enable you to make more secure and controllable functions than you could with CFScript.

There is at least one disadvantage to using CFML and CFUNCTION:

  • In CFML, you are forced to scope your variables using the this statement.

In most cases, developers familiar with UDF from ColdFusion 5.0 find themselves more comfortable with developing UDFs in CFScript; most other developers find using CFFUNCTION and a choice of CFML or CFScript to be the most productive way of developing UDFs. You should choose what you find to be the most productive and comfortable for you.

Advanced UDF Concepts

Your advanced UDFs should work with an unknown amount of arguments or parameters as well as with ones that are required and ones that are not required. An example of this is your creating a UDF that sums any amount of numbers passed to it. With CFScript, you need to define the required arguments first when defining the function. For example:

 function Sum(Arg1,Arg2) 

This makes the arguments arg1 and arg2 required by default. Assume that you wrote only the following:

 function Sum() 

This code states there are no required parameters. The problem then becomes how you work with this unknown number of arguments. Well, with UDFs, all arguments are stored in a special arguments variable scope, and in the case of CFScript-based UDFs, these arguments can be accessed from the arguments scope as an array.

Let's look at a simple example that clarifies how to work with arguments in CFScript. In our example, we are going to create a UDF that enables us to multiply any quantity of numbers passed to it.

Listing 5.3 MultiplyList.cfm
 <cfscript>  function MultiplyList()  {    var arg_count = ArrayLen(Arguments);    var sum = 1;    var i = 1;    for( i = 1 ; i LTE arg_count; i = i + 1 )    {      sum = sum * Arguments[i];    }    return sum;  }  </cfscript>  <cfset val1 = 100>  <cfset val2 = 3>  <cfset val3 = 3>  <cfset val4 = 6>  <cfset theVal =  MultiplyList(val1,val2,val3,val4)>  <cfoutput>#theVal#:</cfoutput> 

Let's break down this code listing. In the first line, we see that we defined the function and required no arguments. Next, we do something very useful, which is to define the length of the list of arguments (that is, how many elements are in the list) that we are using.

 var arg_count = ArrayLen(Arguments); 

This simply sets the arg_count (using the array length function ArrayLen) to the size of the arguments list. We then create a for loop that sums the list of arguments in the list and then return the sum. Now we could just as easily redo the function line in the code to be the following:

 function MultiplyList(Arg1, Arg2) 

This code forces us to supply at least two numbers to multiply. The last few lines then set a series of values, which we then pass to the function to operate on.

You can play with the code and extract values or add values. You will see that it multiplies any number of values.

Let's now do the same thing using CFFUNCTION. With CFFUNCTION, we have to do things a little differently. With CFFUNCTION, we have to access our arguments as we would a structure.

Listing 5.4 MultiplyListn.cfm
 <cffunction name="MultiplyListn">    <cfset this.sum = 1>    <cfloop collection="#Arguments#" item="argument">      <cfset this.sum = this.sum * Arguments[argument]>    </cfloop>    <cfreturn this.sum>  </cffunction>    <cfset val1 = 3>  <cfset val2 = 3>  <cfset val3 = 3>  <cfset val4 = 6>  <cfset theVal =  MultiplyListn(val1,val2,val3,val4)>  <cfoutput>#theVal#:</cfoutput> 

The code should be obvious except that you should notice we are using the variable scope this. With CFFUNCTION, whenever you set a variable, it is then set to the page or request scope; thus, it is available to that page. It can be awkward if you are creating UDFs that might have variable names with the same name as other variables in your application because the UDF might overwrite those variables. In addition, the pages in which you are including your UDF might overwrite your UDFs variables. To make sure this does not happen, you should use the this scope to scope your variables to the UDF only!

A Note on this

The this scope is equivalent to local variables that you defined using the var statement in CFScript. However, unlike with CFScript, you must use the scope identifier this when you create or use the variable!


Let's try an even more complex example so that we can see a more sophisticated usage of UDF functionality. We are going to build a generic "data structure" called a stack. Simply put, data structures are systematic ways of organizing and accessing data.

We are going to build a data structure, called a stack, that is based on the principle of LIFO (last in, first out). Think of a stack as a data PEZ dispenser; instead of PUSH-ing PEZ into it, you are pushing data into it. When you want to POP out a PEZ, you are actually POP-ing out data.

Stacks are often used by applications such as your web browser. When you visit a site, a URL is PUSH-ed into the stack, and when you click the Back button, it is POP-ed out.

Let's look at the example shown in Listing 5.5.

Listing 5.5 gStack.cfm
 <cfapplication name="gStack" clientmanagement="yes"  sessionmanagement="yes" >  <cffunction name="gStack">    <!---   This function expects a mandatory argument and a optional argument which is some value.    You have four actions you can perform with gStack:    1.  PUSH - this argument tells the stack to take another number and put it to the  top of the stack and then push any existing values 1 row down the stack.    2.  TOP  - All this does is return the value at the very top of the stack    3.  POP  - This return's the top value of the stack although deleteing it from the  stack.    4.  SIZE - Returns the size of the stack     --->    <cfargument name="actOption" default="PUSH">    <cflock scope="session" type="exclusive" timeout="10" throwontimeout="No">      <cfif NOT StructKeyExists(session, "stack")>        <cfset session.stack = ArrayNew(1)>      </cfif>      <cfset size = ArrayLen(session.stack)>      <cfswitch expression="#UCase(arguments.actOption)#">      <cfcase value="PUSH">         <cfif StructKeyExists(arguments, "1")>          <cfif ArrayLen(session.stack)>            <cfset temp = ArrayInsertAt(session.stack, 1, arguments.1)>          <cfelse>            <cfset session.stack[1] = arguments.1>          </cfif>          <cfreturn arguments.1>        <cfelse>          <cfreturn "error you must pass a value when you use PUSH!">        </cfif>      </cfcase>      <cfcase value="TOP">        <cfif Val(size)>          <cfreturn session.stack[1]>        <cfelse>          <cfreturn "error empty array!">        </cfif>      </cfcase>       <cfcase value="SIZE">        <cfset gSize = ArrayLen(session.stack)>        <cfreturn gSize>      </cfcase>      <cfcase value="POP">        <cfif Val(size)>          <cfif StructKeyExists(arguments, "1") AND IsNumeric(arguments.1)>            <cfset popReturn = session.stack[arguments.1]>            <cfset temp = ArrayDeleteAt(session.stack, arguments.1)>            <cfreturn popReturn>            <cfelse>            <cfreturn "error you must pass a numeric value  when you use POP!">            <cfabort>          </cfif>        <cfelse>          <cfreturn "error empty array!">        </cfif>      </cfcase>      </cfswitch>    </cflock>  </cffunction>  

Although this UDF is a little longer than the others on which we have worked, it's not that much different. In this one, we have turned on session variables so that we can have some persistence of data. We then define the function as well as require an argument that must be passed, which has to be PUSH, POP, TOP, or SIZE; we have also set it to a default of PUSH. The rest of the code is SWITCH and CASE statements, which handle the different options that can be chosen.

The next listing is a simple form that passes information to itself and calls the gStack function.

Listing 5.6 test.gStack.cfm
 <cfinclude template="gStack.cfm">  <cfparam name="form.stkvalue" default="">  <cfparam name="form.opt" default="PUSH">  <cfparam name="variables.value" default="">  <cfscript>    if(IsDefined('gStack'))      if(IsCustomFunction(gStack))        WriteOutput('gStack is a user-defined function');      else        WriteOutput('gStack is defined but is NOT a user-defined function');    else       WriteOutput('gStack is not defined');  </cfscript>  <br>  <cfoutput>  <form action="#cgi.script_name#" method="post">    <input name="stkvalue"  value="">    <select name="opt"  size="1">      <option value="PUSH">PUSH</option>      <option value="POP">POP</option>      <option value="TOP">TOP</option>      <option value="SIZE">SIZE</option>    </select>    <input type="submit" value="Submit Query">    <cfif UCase(cgi.request_method) EQ "POST">      <p>The action performed was: #form.opt#</p>      <cfswitch expression="#UCase(form.opt)#">      <cfcase value="PUSH">         <cfset t = gStack(form.opt, form.stkvalue)>      </cfcase>      <cfcase value="POP">        <cfset variables.value = gStack(form.opt, form.stkvalue)>        <p>The Value was: #variables.value#</p>      </cfcase>      <cfcase value="TOP,SIZE">        <cfset variables.value = gStack(form.opt)>        <p>The Value was: #variables.value#</p>      </cfcase>      </cfswitch>    </cfif>  </form>  </cfoutput>  

Now the only interesting thing about this listing, besides the fact that we are interacting with our function using four variables, is the CFScript block that uses the function IsCustomFunction. This function enables us to check whether a UDF has been properly loaded or defined.

You often need to pass complex data types to and from your UDFs. Let's change our stack test a little bit so that it accepts a structure in the form of an order and stores it in the stack.

Listing 5.7 test2.gStack.cfm
 <cfinclude template="gStack.cfm">  <cfparam name="form.ProductName" default="">  <cfparam name="form.Vendor" default="">  <cfparam name="form.Price" default="">  <cfparam name="form.quantity" default="">  <cfparam name="variables.value" default="">  <cfscript>    if(IsDefined('gStack'))      if(IsCustomFunction(gStack))        WriteOutput('gStack is a user-defined function');      else        WriteOutput('gStack is defined but is NOT a user-defined function');    else        WriteOutput('gStack is not defined');  </cfscript>  <br>  <cfoutput>    <cfif UCase(cgi.request_method) EQ "POST">      <cfscript>        newOrder = StructNew();        newOrder.productName = form.ProductName;        newOrder.Vendor = form.Vendor;        newOrder.Price = form.Price;        newOrder.quantity = form.quantity;        variables.value = gStack('PUSH', newOrder);      </cfscript>      <cfdump var="#variables.value#">    <cfelse>      <form action="#cgi.script_name#" method="post">      <table>      <tr>        <td>Product</td>        <td>Vendor</td>        <td>Price</td>        <td>Amount</td>      </tr>      <tr>         <td><input type="text" name="ProductName"  value=""></td>        <td><input type="text" name="Vendor"  value=""></td>        <td><input type="text" name="Price"  value=""></td>        <td><input type="text" name="quantity"  value=""></td>      </tr>      </table>      <input type="submit" name="submit"  value="Add to Cart">      </form>    </cfif>  </cfoutput>  <cfdump var="#session#">  

All we have done in this section is create a simple self-referencing form that lets us pass product information, which is then turned into a ColdFusion structure and stored using the gStack UDF. We then can see what gStack returns by using CFDUMP to see the returned structure that is now sitting at the top of the stack. You can easily pass back and forth between UDFs any ColdFusion data type, but you need to remember that they can return only one variable. Thus, if you need to return complex data, you might have to build a complex data structure, such as an array of structures or a recordset using the CFRETURN tag or statement.

If you are up for it, try creating a whole shopping cart application using gStack as a foundation. The cart should let you to add items, remove them, and so on.

Usage Considerations for UDFs

Before you start writing huge libraries of UDF functions, carefully consider who you want to use your UDFs and how you want to use them. As you have seen, you can deploy your UDFs by using a simple CFINCLUDE or by using/defining your UDF inline. In reality, though, when you are working on large applications, you most likely will be using a group of UDFs often. For example, a UDF that models a typical shopping cart might be used constantly in a certain portion of an application and you would not want to have to include the file every time on every page that you want to use. In this case, you should define your UDF in your application.cfm.

If you use dozens and dozens of UDFs, define them all on one or more templates and then include those templates in your application.cfm. This would most commonly be done if you wanted to define whole libraries of UDFs and categorize them by functional group. That way, you would have one template that defined all your security UDFs, one your Extensible Markup Language UDFs, and one your validation UDFs. Then you include these in your application.cfm so that they are available to the whole application all the time.



Inside ColdFusion MX
Inside Coldfusion MX
ISBN: 0735713049
EAN: 2147483647
Year: 2005
Pages: 579

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net