Defining and Using Custom Functions

You can define custom functions (also known as user-defined functions) and use them in your application pages as you do standard ColdFusion functions. This allows you to create a function for an algorithm or procedure that you use frequently, and then use the function wherever you need the procedure. If you must change the procedure, you change only one piece of code. You can use your function anywhere that you can use a ColdFusion expression: in tag attributes, between # signs in output, and in CFScript code.

Defining functions

You define functions using CFScript, in a manner similar to defining JavaScript functions. The function must return a value. Functions can be recursive, that is, the body of a function can call the function.

You can define a function in the following places:

Syntax

Use the following syntax inside a cfscript tag to define a function:


function functionName( [paramName1[, paramName2...]] )

{

  CFScript Statements

} 

functionName

The name of the function. You cannot use the name of an standard ColdFusion function name. You cannot use the same name for two different function definitions. Function names cannot include periods.

paramName1...

Names of the parameters required by the function. The number of arguments passed into the function must equal or exceed the number of parameters in the function definition. If the calling page omits any of the required parameters, ColdFusion generates a mismatched argument count error.

The body of the function definition must consist of one or more valid CFScript statements.

The following two statements are allowed only in function definitions. Each function must have a return statement:

var variableName = initialValue;

Creates and initializes a variable that is local to the function (function variable). This variable has meaning only inside the function and is not saved between calls to the function. It has precedence in the function body over any variables with the same name that exist in any other scopes. You never prefix a function variable with a scope identifier, and their names cannot include periods.

All var statements must be at the top of the function declaration, before any other statements. You must initialize all variables when you declare them. You cannot use the same name for a function variable and a parameter.

return expression;

Evaluates expression, returns its value to the page that called the function, and exits the function. You can return any valid ColdFusion variable type, including structures, queries, and arrays.

Each function must execute a return statement.

Calling functions

You can call a function anywhere that you can use an expression, including in the body of a cfoutput tag, in a ColdFusion Script, or in a tag attribute value. One function can call another function, and you can use a function as a parameter to another function.

You call custom functions the same way you call any built-in ColdFusion functions.

Using arguments and variables

The following sections discuss optional arguments and their use, how arguments get passed, including the effects that the passing methods have, and how you use variables inside functions.

Optional arguments and the Arguments array

A function can have optional arguments that you do not specify as parameters in the definition. For example, you can write the following function that requires two arguments and supports three additional optional arguments:

function MyFunction(MyArg1, MyArg2)

Any of the following function calls are then valid:

MyFunction(Value1, Value2)

MyFunction(Value1, Value2, Value3)

MyFunction(Value1, Value2, Value3, Value4)

MyFunction(Value1, Value2, Value3, Value4, Value5)

Each function has a built-in Arguments array containing all arguments passed to the function: the required arguments specified by the function parameters followed by any additional arguments included in the function call.

The function can determine the number of arguments passed to it by using the ColdFusion function call ArrayLen(Arguments).

The function must retrieve the optional arguments by using the Arguments array. For example, if the following function:

function MyFunction(MyArg1, MyArg2)

has three optional arguments, you can refer to the first two, required, arguments as MyArg1 and MyArg2 or as Arguments[1] and Arguments[2]. You must refer to the third and fourth and fifth, optional, arguments as Arguments[3], Arguments[4], and Arguments[5].

However, you must be careful if you mix references to the same argument using both its parameter name and its place in the Arguments array. Mixing is fine if all you do is read the argument. If you write the argument, you should consistently use either the parameter name or the Arguments array.

Passing arguments

ColdFusion passes arrays and simple data types including integers, strings, and time and date values into the function by value. It passes queries and structures into the function by reference. As a result, if you change the value of a query or structure argument variable in the function, it changes the value of the variable that the calling page used in calling the function. However, if you change the value of an array or string argument variable in the function, it does not change the value of the string variable in the calling page.

Using variables

A function can access all variables that are available in the calling page. In addition, the function has its own private scope that contains the function parameters, the Arguments array, and the var-declared variables. This scope is only accessible inside the current instance of the function. As soon as the function exits, all the variables are removed.

A function can use and change any variable that is available in the calling page, including variables in the caller's Variables (local) scope, as if the function were part of the calling page. For example, if you know that the calling page has a local variable called Customer_name (and there is no var variable named Customer_name) the function can read and change the variable by referring to it as "Customer_name" or (using better coding practice) "Variables.Customer_name".

Similarly, you can create a local variable inside a function and then refer to it anywhere in the calling page after the function call. You cannot refer to the variable before you call the function.

Because function var variables do not take a scope identifier and exist only while the function executes, function variable names can be independent of variable names used elsewhere in your application. If a function must use a variable from another scope that has the same name as a function variable, just prefix the external variable with its scope identifier, such as Variables or Form.

For example, if you use the variable name x for a function scope (var) variable and for a Variables scope (local) variable in the body of a page that calls the function, the two variables are independent of each other. You cannot refer to the function variable in the body of the page, but you can refer to the local variable in the function as Variables.x.

Identifying custom functions

You can use the IsCustomFunction function to determine whether a name represents a custom function. The function throws an error if its argument is not defined as a ColdFusion object. As a result, if your code context does not ensure that the name exists, you should use the isDefined function to ensure that it is defined. For example:

<cfscript>

if( IsDefined("MyFunc"))

  if( IsCustomFunction( MyFunc ))

    WriteOutput( "MyFunc is a custom function");

  else

    WriteOutput( "Myfunc is defined but is NOT a custom function");

else

  WriteOutput( "MyFunc is not defined");

</cfscript>

Note that the you do not surround the argument to IsCustomFunction in quotation marks.

Examples of custom functons

The following simple examples show the use of custom functions.

Example 1

This function takes a principal amount, an annual percentage rate, and a loan duration in months and returns the total amount of interest to be paid over the period. You can optionally use the percent sign for the percentage rate, and include the dollar sign and comma separators for the principal amount.

<cfscript>

function TotalInterest(principal, annualPercent, months)

{

  Var years = 0;

  Var interestRate = 0;

  Var totalInterest = 0;

  principal = trim(principal);

  principal = REReplace(principal,"[\$,]","","ALL");

  years = months / 12;

  annualPercent = Replace(annualPercent,"%","","ALL");

  interestRate = annualPercent / 100;

  totalInterest = principal*(((1+ interestRate)^years)-1);

  Return DollarFormat(totalInterest);

}

</cfscript>

You could use the TotalInterest function in a cfoutput tag of a form's action page as follows:

<cfoutput> 

Loan amount: #Form.Principal#<br>

Annual percentage rate: #Form.AnnualPercent#<br>

Loan duration: #Form.Months# months<br>

TOTAL INTEREST: #TotalInterest(Form.Principal, Form.AnnualPercent, Form.Months)#<br>

</cfoutput>

Example 2

This function shows the use of optional arguments. It takes two or more arguments and adds them together.

<cfscript>

function Sum2(a,b)

{      

    var sum = a + b;  

    var arg_count = ArrayLen(Arguments);

    var opt_arg = 3;

    for( ; opt_arg LTE arg_count; opt_arg = opt_arg + 1 )

    {

      sum = sum + Arguments[opt_arg];

    }

    return sum; 

} 

</cfscript>

Using custom functions effectively

These notes provide information that will help you use custom functions more effectively.

Using Application.cfm

Consider putting custom functions that you use frequently on the Application.cfm page.

Queries as function parameters

When you call a custom function in the body of a tag that has a query attribute, such as cfloop query=... tag, a query column name parameter is normally passed as a single element of the column, not the entire column. Therefore, functions that manipulate query data and are called within the bodies of ColdFusion tags with query attributes should only manipulate one query row.

You can use functions that manipulate many rows of a query outside such tags. There you can pass in a query and loop over it in the function. The following example, which changes text in a query column to uppercase, illustrates using a function to modify multiple query rows.

function UCaseColumn(myquery, colName)

{

  var currentRow = 1;

  for (; currentRow lte myquery.RecordCount; 

    currentRow = currentRow + 1)

  {

    myquery[colName][currentRow] =

      UCase(myquery[colName][currentRow]);

  }

}

Evaluating strings in functions

If your custom function evaluates parameters that contain strings, you must make sure that all variable names in the string are fully qualified to avoid conflicts with function local variables. In the following example, you get the expected results if you pass the fully qualified argument, Variables.myname, but you get the unexpected function local variable value if you pass the argument unqualified, as myname.

<CFScript>

  myname = "globalName";

  function readname( name )

  {

    var myname = "localName";

    return (Evaluate( name ));

  }

  </CFScript>



  <cfoutput>

  The result of calling readname with "myname" is: 

      #readname("myname")# <br>

<!--- whoops, collides with local variable name --->

  The result of calling readname with "variables.myname" is:  #readname("variables.myname")#  

<!--- ok. Finds the name passed in  --->

  </cfoutput>

Passing arrays to custom functions

Arrays, unlike structures, are passed to custom functions by value. This means the function gets a new copy of the array and the array in the calling page is unchanged by the function. For more efficiency, and if you want a function to modify an array in the calling page, store an array as a member of a structure, pass the structure, and reference the array through the structure.

The following example passes an array and a structure containing the array to a function. The function changes the array contents using both of the parameters. The code calls the function and displays the resulting array contents. The change the function makes using the structure appears, but the change the function makes using the directly passed array does not affect the array outside the function.

<CFScript>

  //Create a two-element array inside a structure

  mystruct = StructNew();

  mystruct.myarray = ArrayNew(1);;

  mystruct.myarray[1] = "This is the original element one";

  mystruct.myarray[2] = "This is the original element two";



  //Define a custom function to manipulate both

  //an array in a structure (using struct_param) and

  //an array that is passed directly (using array_param).

  function setarray( struct_param, array_param )

  {

    //Change the first element of the array passed in the structure

    struct_param.myarray[1] = "This is the NEW element one";

    //Change the seond element of the directly-passed array

    array_param[2] = "This is the NEW element two";

    return "success";

  }

  //Call the function passing the structure and the array

  setarray( mystruct, mystruct.myarray);

</CFScript>



<CFOutput>

<!--- The element one is changed --->

  <br>#mystruct.myarray[1]# 

<!--- Element two is unchanged because the function got a copy --->

  <br>#mystruct.myarray[2]#

</CFoutput>

Error handling

You can handle errors in custom functions by writing a status variable indicating success or failure and some information about the failure. You can also return a special value to indicate failure. The following sketch outlines possible combinations of both these approaches:

function bigCalc(x, y, errorInfo)

{

  // Clear error state

  // This allows errorInfo struct to be reused

  structClear(errorInfo);

  

  var isOK = true;

  

  // Do work, populate fields in errorInfo such as 

  // errorNumber, errorMsg, errorDetail, whatever

  ...

  

  if (isOK)

  {

    return calculatedValue;

  }

  else

  {

    // Need to return error value

    // Caller will look at value and then decide to look at errorInfo

    // Alternatively, caller can look at errorInfo and see whether 

    // some pre-defined fields are available

    return -1; // or "" or whatever we have agreed to...

  }

}



errorInfo = structNew();



result = bigCalc(x, y, errorInfo);



if (result eq -1)

{

  // use information from errorInfo

}





or



if (structKeyExists(errorInfo, "errorMsg"))

{

  // error

}



anotherResult = bigCalc(x + 10, y + 30);



...



Banner.Novgorod.Ru