Dynamic expressions and dynamic variables

This section discusses the advanced topics of dynamic expressions, dynamic evaluation, and dynamic variable naming. Many ColdFusion programmers never encounter or need to use dynamic expressions. However, dynamic variable naming is important in situations where the variable names are not known in advance, such as in shopping cart applications.

This section also discusses the use of the IIF function which is most often used without dynamic expressions. This function dynamically evaluates its arguments, and you must often use the DE function to prevent the evaluation. For more information on using the IIF function, see "Using the IIF function".

Note:   This section uses several tools and techniques that are documented in later chapters. If you are unfamiliar with using ColdFusion forms, structures, and arrays, you should learn about these tools before reading this section.

About dynamic variables

Dynamic variables are variables that are named dynamically, typically by creating a variable name from a static part and a variable part. For example, the following example dynamically constructs the variable name from a variable prefix and a static suffix:

<cfset "#flavor#_availability" = "out of stock">

Using dynamic variables in this manner does not require dynamic evaluation.

About dynamic expressions and dynamic evaluation

In a dynamic expression, the actual expression, not just its variable values, is determined at execution time. In other words, in a dynamic expression the structure of the expression, such as the names of the variables, not just the values of the variables, gets built at runtime.

You create dynamic expressions using string expressions, which are expressions contained in strings, (that is, surrounded with quotation marks). Dynamic evaluation is the process of evaluating a string expression. The Evaluate and IIF functions, and only these functions, perform dynamic evaluation.

When ColdFusion performs dynamic evaluation it does the following:

  1. Takes a string expression and treats it as a standard expression, as if the expression was not a string.
  2. Parses the expression to determine the elements of the expression and validate the expression syntax.
  3. Evaluates the expression, looking up any variables and replacing them with their values, calling any functions, and performing any required operations.

This process enables ColdFusion to interpret dynamic expressions with variable parts. However, it incurs a substantial processing overhead.

Dynamic expressions were important in early versions of ColdFusion, before it supported arrays and structures, and they still can be useful in limited circumstances. However, the ability to use structures and the ability to use associative array notation to access structure elements provide more efficient and easier methods for dynamically managing data. For information on using arrays and structures, see Chapter 5, "Using Arrays and Structures".

Selecting how to create variable names

The following two examples describes cases when you need dynamic variable names:

In both cases, it might appear that dynamic expressions using the Evaluate function are needed to construct the variable names. However, you can achieve the same ends more efficiently by using dynamic variable naming, as shown in "Example: a dynamic shopping cart".

This does not mean that you must always avoid dynamic evaluation. However, given the substantial performance costs of dynamic evaluation, you should first ensure that one of the following techniques cannot serve your purpose:

Dynamic variable naming without dynamic evaluation

While ColdFusion does not always allow you to construct a variable name in-line from variable pieces, it does let you to do so in the most common uses, as described in the following sections.

Using pound signs to construct a variable name in assignments

You can combine text and variable names to construct a variable name on the left side of a cfset assignment. For example, the following code sets the value of the variable Product12 to the string "Widget":

<cfset ProdNo = 12>
<cfset "Product#ProdNo#" = "Widget">

To construct a variable name this way, all the text on the left side of the equal sign must be in quotation marks.

This usage is less efficient than using arrays. The following example has the same purpose as the previous one, but requires less processing:

<cfset MyArray=ArrayNew(1)>
<cfset prodNo = 12>
<cfset myArray[prodNo] = "Widget">

Dynamic variable limitation

When you use a dynamic variable name in quotes on the left side of an assignment, the name must be either a simple variable name or a complex name that uses object.property notation (such as MyStruct.#KeyName#). You cannot use an array as part of a dynamic variable name. For example, the following code generates an error:

<cfset MyArray=ArrayNew(1)>
<cfset productClassNo = 1>
<cfset productItemNo = 9>
<cfset "myArray[#productClassNo##productItemNo#]" = "Widget">

However, you can construct an array index value dynamically from variables without using quotes on the left side of an assignment. For example, the preceding sample code works if you replace the final line with the following line:

<cfset myArray[#productClassNo# & #productItemNo#] = "Widget">

Dynamically constructing structure references

The ability to use associative array notation to reference structures provides a way for you to use variables to dynamically create structure references. (For a description of associative array notation, see "Structure notation," in Chapter 5.) Associative array structure notation allows you to use a ColdFusion expression inside the index brackets. For example, if you have a productName structure with keys of the form product_1, product_2 and so on, you can use the following code to display the value of productName.product_3:

<cfset prodNo = 3>
<cfoutput>
  Product_3 Name: #ProductName["product_" & prodNo]#
<cfoutput>

For an example of using this format to manage a shopping cart, see "Example: a dynamic shopping cart".

Using dynamic evaluation

The following sections describe how to use dynamic evaluation and create dynamic expressions.

ColdFusion dynamic evaluation functions

The following table describes the functions that perform dynamic evaluation and are useful in evaluating dynamic expressions:
Function
Purpose
DE
Escapes any double quotes in the argument and wraps the result in double quotes. The DE function is particularly useful with the IIF function, to prevent the function from evaluating a string to be output.
For an example of using the DE function with the IIF function, see "Using the IIF function".
Evaluate
Takes one or more string expressions and dynamically evaluates their contents as expressions from left to right. (The results of an evaluation to the left can have meaning in an expression to the right.) Returns the result of evaluating the rightmost argument.
For more information on this function see "About the Evaluate function".
IIF
Evaluates a boolean condition expression. Depending on whether this expression is True or False, dynamically evaluates one of two string expressions and returns the result of the evaluation. The IIF function is convenient for incorporating a cfif tag in-line in HTML.
For an example of using this function, see "Using the IIF function".
SetVariable
Sets a variable identified by the first argument to the value specified by the second argument. This function is no longer required in well-formed ColdFusion pages; see "SetVariable function considerations".

Function argument evaluation considerations

It is important to remember that ColdFusion always evaluates function arguments before the argument values are passed to a function:

For example, consider the following DE function:

<cfoutput>#DE("1" & "2")#</cfoutput>

You might expect this line to display """1"" & ""2""". Instead, it displays "12", because ColdFusion processes the line as follows:

  1. Evaluates the expression "1" & "2" as the string "12".
  2. Passes the string "12" (without the quotes) to the DE function.
  3. Calls the DE function, which adds literal quotation marks around the 12.

Similarly, if you use the expression DE(1 + 2), ColdFusion evaluates 1 + 2 as the integer 3 and passes it to the function. The function converts it to a string and surrounds the string in literal quotation marks: "3".

About the Evaluate function

The following example can help you understand the Evaluate function and how it works with ColdFusion variable processing:

<cfset myVar2="myVar">
<cfset myVar="27/9">
<cfoutput>
  #myVar2#<br>
  #myVar#<br>
  #Evaluate("myVar2")#<br>
  #Evaluate("myVar")#<br>
  #Evaluate(myVar2)#<br>
  #Evaluate(myVar)#<br>
</cfoutput>

Reviewing the code

The following table describes how ColdFusion processes this code:
Code
Description
<cfset myVar2="myVar">
<cfset myVar="27/9">
Sets the two variables to the following strings:
myVar
27/9
<cfoutput>
  #myVar2#<br>
  #myVar#<br>
Displays the values assigned to the variables, myVar and 27/9 respectively.
  #Evaluate("myVar2")#<br>
Passes the string "myvar2" (without the quotes) to the Evaluate function, which does the following:
  1. Evaluates it as the variable myVar2.
  2. Returns the value of the myVar2 variable, the string "myvar" (without the quotes).
  #Evaluate("myVar")#<br>
Passes the string "myvar" (without the quotes) to the Evaluate function, which does the following:
  1. Evaluates it as the variable myVar.
  2. Returns the value of the myVar variable, the string "27/9" (without the quotes).
  #Evaluate(myVar2)#<br>
Evaluates the variable myVar2 as the string "myVar" and passes the string (without the quotes) to the Evaluate function. The rest of the processing is the same as in the previous line.
  #Evaluate(myVar)#<br>
</cfoutput>
Evaluates the variable myVar as the string "27/9" (without the quotes), and passes it to the Evaluate function, which does the following:
  1. Evaluates the string as the expression 27/9
  2. Performs the division.
  3. Returns the resulting value, 3.

As you can see, using dynamic expressions can result in substantial expression evaluation overhead, and the code can be confusing. Therefore, you should avoid using dynamic expressions wherever a simpler technique, such as using indexed arrays or structures can serve your purposes.

Avoiding the Evaluate function

Using the Evaluate function increases processing overhead, and in most cases it is not necessary. The following sections provide examples of cases where you might consider using the Evaluate function.

Example 1

You might be inclined to use the Evaluate function in code such as the following:

<cfoutput>1 + 1 is #Evaluate(1 + 1)#</cfoutput>

Although this code works, it is not as efficient as the following code:

<cfset Result = 1 + 1>
<cfoutput>1 + 1 is #Result#</cfoutput>

Example 2

This example shows how you can use an associative array reference in place of an Evaluate function. This technique is powerful because:

The following example uses the Evaluate function to construct a variable name:

<cfoutput>
Product Name: #Evaluate("Form.product_#i#")#
</cfoutput>

This code comes from an example where a form has entries for an indeterminate number of items in a shopping cart. For each item in the shopping cart there is a product name field. The field name is of the form product_1, product_2, and so on, where the number corresponds to the product's entry in the shopping cart. In this example, ColdFusion does the following:

  1. Replaces the variable i with its value, for example 1.
  2. concatenates the variable value with "Form.product_", and passes the result (for Form.product_1) to the Evaluate function, which does the remaining steps.
  3. Parses the variable product_1 and generates an executable representation of the variable. Because ColdFusion must invoke its parser, this step requires substantial processing, even for a simple variable.
  4. Evaluates the representation of the variable, for example as "Air popper".
  5. Returns the value of the variable.

The following example has the same result as the preceding example and is more efficient:

<cfoutput>
ProductName: #Form["product_" & i]#
</cfoutput>

In this code, ColdFusion does the following:

  1. Evaluates the expression in the associative array index brackets as the string "product_" concatenated with the value of the variable i.
  2. Determines the value of the variable i; 1.
  3. Concatenates the string and the variable value to get product_1.
  4. Uses the result as the key value in the Form structure to get Form[product_1]. This associative array reference accesses the same value as the object.attribute format reference Form.product_1; in this case, Air popper.

This code format does not use any dynamic evaluation, but it achieves the same effect, of dynamically creating a structure reference by using a string and a variable.

SetVariable function considerations

You can avoid using the SetVariable function by using a format such as the following to set a dynamically named variable. For example, the following lines are equivalent:

<cfset SetVariable("myVar" & i, myVal)>

<cfset "myVar#i#" = myVal>

In the second line, enclosing the myVar#i# variable name in quotation marks tells ColdFusion to evaluate the name and process any text in pound signs as a variable or function. ColdFusion replaces the #i# with the value of the variable i, so that if the value of i is 12, this code is equivalent to the line

<cfset myVar12 = myVal>

For more information on this usage, see "Using pound signs to construct a variable name in assignments".

Using the IIF function

The IIF function is a shorthand for the following code:

<cfif argument1>
  <cfset result = Evaluate(argument1)>
<cfelse>
  <cfset result = Evaluate(argument2)>
</cfif>

The function returns the value of the result variable. It is comparable to the use of the JavaScript and Java ? : operator, and can result in more compact code. As a result, the IIF function can be convenient even if you are not using dynamic expressions.

The IIF function requires the DE function to prevent ColdFusion from evaluating literal strings, as the following example shows:

<cfoutput>
#IIf(IsDefined("LocalVar"), "LocalVar", DE("The variable is not
defined."))#
</cfoutput>

If you do not enclose the string "The variable is not defined." in a DE function, the IIF function tries to evaluate the contents of the string as an expression and generates an error (in this case, an invalid parser construct error).

The IIF function is useful for incorporating ColdFusion logic in-line in HTML code, but it entails a processing time penalty in cases where you do not otherwise need dynamic expression evaluation.

The following example shows using IIF to alternate table row background color between white and gray. It also shows the use of the DE function to prevent ColdFusion from evaluating the color strings.

<cfoutput>
<table border="1" cellpadding="3">
<cfloop index="i" from="1" to="10">
  <tr bgcolor="#IIF( i mod 2 eq 0, DE("white"), DE("gray") )#">
    <td>
      hello #i#
    </td>
  </tr>
</cfloop>
</table>
</cfoutput>

This code is more compact than the following example which does not use IIF or DE.

<cfoutput>
<table border="1" cellpadding="3">
<cfloop index="i" from="1" to="10">
  <cfif i mod 2 EQ 0>
    <cfset Color = "white">
  <cfelse>
    <cfset Color = "gray">
  </cfif>
  <tr bgcolor="#color#">
    <td>
      hello #i#
    </td>
  </tr>
</cfloop>
</table>
</cfoutput>

Example: a dynamic shopping cart

The following example dynamically creates and manipulates variable names without using dynamic expression evaluation by using associative array notation.

You need to dynamically generate variable names in applications such as shopping carts, where the required output is dynamically generated and variable. In a shopping cart, you do not know in advance the number of cart entries or their contents. Also, because you are using a form, the action page only receives Form variables with the names and values of the form fields.

The following example shows the shopping cart contents and lets you edit your order and submit it. To simplify things, the example automatically generates the shopping cart contents using CFScript instead of having the user fill the cart. A more complete example would populate a shopping cart as the user selected items. Similarly, the example omits all business logic for committing and making the order.

To create the form:

  1. Create a file in your editor.
    <html>
    <head>
      <title>Shopping Cart</title>
    </head>
    <cfscript>
    CartItems=4;
    Cart = ArrayNew(1);
    for ( i=1; i LE cartItems; i=i+1)
    { 
      Cart[i]=StructNew();
      Cart[i].ID=i;
      Cart[i].Name="Product " & i;
      Cart[i].SKU=i*100+(2*i*10)+(3*i);
      Cart[i].Qty=3*i-2;
    }
    </cfscript>
    
    <body>
    Your shopping cart has the following items.<br>
    You can change your order quantities.<br>
    If you don't want any item, clear the item's check box.<br>
    When you are ready to order, click submit.<br>
    <br>
    <cfform name="ShoppingCart" action="ShoppingCartAction.cfm" method="post">
      <table>
      <tr>
        <td>Order?</td>
        <td>Product</td>
        <td>Code</td>
        <td>Quantity</td>
      </tr>
      <cfloop index="i" from="1" to="#cartItems#">
        <tr>
          <cfset productName= "product_" & Cart[i].ID>
          <cfset skuName= "sku_" & Cart[i].ID>
          <cfset qtyname= "qty_" & Cart[i].ID>
          <td><cfinput type="checkbox" name="itemID" value="#Cart[i].ID#" checked>
           </td>
          <td><cfinput type="text" name="#productName#" value="#Cart[i].Name#"
           passThrough = "readonly = 'True'"></td>
          <td><cfinput type="text" name="#skuName#" value="#Cart[i].SKU#"
           passThrough = "readonly = 'True'"></td>
          <td><cfinput type="text" name="#qtyName#" value="#Cart[i].Qty#">
           </td>
        </tr>
      </cfloop>
      </table>
      <input type="submit" name="submit" value="submit">
    </cfform>
    
    </body>
    </html>
    
  2. Save the page as ShoppingCartForm.cfm.

Reviewing the code

The following table describes the code:
Code
Description
<cfscript>
CartItems=4;
Cart = ArrayNew(1);
for ( i=1; i LE #cartItems#; i=i+1)
{ 
  Cart[i]=StructNew();
  Cart[i].ID=i;
  Cart[i].Name="Product " & i;
  Cart[i].SKU=i*100+(2*i*10)+(3*i);
  Cart[i].Qty=3*i-2;
}
</cfscript>
Create a shopping cart as an array of structures, with each structure containing the cart item ID, product name, SKU number, and quantity ordered for one item in the cart. Populate the shopping cart by looping CartItems times and setting the structure variables to arbitrary values based on the loop counter. A real application would set the Name, SKU, and Quantity values on other pages.
<cfform name="ShoppingCart"
    action="ShoppingCartAction.cfm"
    method="post">
  <table>
    <tr>
      <td>Order?</td>
      <td>Product</td>
      <td>Code</td>
      <td>Quantity</td>
    </tr>
Start the form and its embedded table. When the user clicks the submit button, post the form data to the ShoppingCartAction.cfm page.
The table formats the form neatly. The first table row contains the column headers. Each following row has the data for one cart item.
  <cfloop index="i" from="1" to="#cartItems#">
    <tr>
      <cfset productName= "product_" &
        Cart[i].ID>
      <cfset skuName= "sku_" & Cart[i].ID>
      <cfset qtyname= "qty_" & Cart[i].ID>
      <td><cfinput type="checkbox"
        name="itemID" value="#Cart[i].ID#"
        checked>
      </td>
      <td><cfinput type="text"
        name="#productName#"
        value="#Cart[i].Name#"
        passThrough = "readonly = 'True'">
      </td>
      <td><cfinput type="text"
        name="#skuName#"
        value="#Cart[i].SKU#"
        passThrough = "readonly = 'True'">
      </td>
      <td><cfinput type="text"
        name="#qtyName#"
        value="#Cart[i].Qty#">
      </td>
    </tr>
  </cfloop>
  </table>
Loop through the shopping cart entries to generate the cart form dynamically. For each loop, generate variables used for the form field name attributes by appending the cart item ID (Cart[i].ID) to a field type identifier, such as "sku_".
Use a single name, "itemID", for all check boxes. This way, the itemID value posted to the action page is a list of all the check box field values. The check box field value for each item is the cart item ID.
Each column in a row contains a field for a cart item structure entry. The passthrough attribute sets the product name and SKU fields to read-only; note the use of single quotes. (For more information on the cfinput tag passthrough attribute, see CFML Reference.) The check boxes are selected by default.
<input type="submit" name="submit"
  value="Submit">
</form>
Create the Submit button and end the form.

To create the Action page:

  1. Create a file in your editor.
  2. Enter the following text:
    <html>
    <head>
      <title>Your Order</title>
    </head>
    <body>
    <cfif isDefined("Form.submit")>
      <cfparam name="Form.itemID" default="">
      <cfoutput>
        You have ordered the following items:<br>
        <br>
        <cfloop index="i" list="#Form.itemID#">
          ProductName: #Form["product_" & i]#<br>
          Product Code: #Form["sku_" & i]#<br>
          Quantitiy: #Form["qty_" & i]#<br>
          <br>
        </cfloop>
      </cfoutput>
    </cfif>
    </body>
    </html>
    
  3. Save the file as ShoppingCartAction.cfm
  4. Open ShoppingCartform.cfm in your browser, change the check box and quantity values, and click Submit.

Reviewing the code

The following table describes the code:
Code
Description
<cfif isDefined("Form.submit")>
Run the CFML on this page only if it is called by submitting a form. This is not needed if there are separate form and action pages, but is required if the form and action page were one ColdFusion page.
<cfparam name="Form.itemID"
  default="">
Set the default Form.itemID to the empty string. This prevents ColdFusion from displaying an error if the user clears all check boxes before submitting the form (so no product IDs are submitted).
<cfoutput>
You haver ordered the following
    items:<br>
<br>
<cfloop index="i" list=
    "#Form.itemID#">
  ProductName:
    #Form["product_" & i]#<br>
  Product Code: 
    #Form["sku_" & i]#<br>
  Quantitiy:
    #Form["qty_" & i]#<br>
  <br>
</cfloop>
</cfoutput>
</cfif>
Display the name, SKU number, and quantity for each ordered item.
The form page posts Form.itemID as a list containing the value attributes of all the check boxes. These attributes contain the shopping cart item IDs for the selected cart items. Use the list values to index a loop that outputs each ordered item.
Use associative array notation to access the Form scope as a structure and use expressions in the array indexes to construct the form variable names. The expressions consist of a string containing the field name's field type prefix (for example, "sku_"), concatenated with the variable i, which contains the shopping cart ItemID number (which is also the loop index variable).

Comments