Closures in ColdFusion

I've been reading Sean Corfield's examples on how to use the Closure library. This still seems a little mystical to me since I haven't made use of closures in programming before.

To ease my befuddlement, I wrote a test for the Closures library. The use case for the below example is summing a column that contains non-numeric values. You can recreate the test by following these steps:

Create a table containing the three columns.

  1. InvoiceID = Primary Key, autonumbered
  2. CreateDate = Date
  3. Amount = varchar 50

Now run the following code to populate the table:

view plain print about
1<cfloop from="100" to="1" step="-1" index="x">
2    <cfset tDate = dateFormat( DateAdd( "d", -x, now()) ) >
3    <cfset tAmount = randRange( 100, 10000) />
4    <cfif x mod 5 IS 0 >
5        <cfset tAmount = 'Not Applicable' />
6    </cfif>
7
8    <cfquery name="setTest" datasource="test">
9        INSERT INTO Invoice
10        ( CreateDate, Amount )
11        VALUES
12        ( <cfqueryparam value="#tDate#" cfsqltype="cf_sql_date">,
13            <cfqueryparam value="#tAmount#" cfsqltype="cf_sql_varchar">
14        )
15    </cfquery>
16
17</cfloop>

You now have 100 rows of data and every 5th row contains the string 'Not Applicable'.

The goal of this is to sum the column 'Amount' and output the result. The closure has an internal scope and can keep the running total. The function body used to create the closure is rather simplistic and takes two arguments, Mode and valueToTotal. The logic of the function simply adds the valueToTotal to the running total. Passing a mode of 'Display' will output the current total.

view plain print about
1<!--- Create the ClosureFactory --->
2<cfset cf = createObject("component","org.corfield.closure.ClosureFactory") />
3
4<!--- Create the runningTotal function --->
5<cffunction name="runningTotal" >
6    <cfargument name="mode" type="string" required="true"/>
7    <cfargument name="valueToTotal" type="string" default="" />
8    
9    <!--- We need an internal scope to hold the total so lets make sure one exists before we access it --->
10    <cfif NOT structKeyExists( variables, "total") >
11        <cfset variables.total = 0 />
12    </cfif>
13
14    <cfif arguments.mode IS "Display">    <!--- Are we trying to display the running total amount --->
15        <cfreturn val( variables.total ) />
16    <cfelse><!--- No, we are passing in a value to total --->
17        <!--- add the value and account for strings by converting to 0 --->
18        <cfset variables.total = variables.total + val( arguments.valueToTotal ) />    
19        <!--- pass back the original value --->
20        <cfreturn arguments.valueToTotal />
21    </cfif>
22
23</cffunction>
24
25<!--- add the closure to a new variable called total. We use total to reference the closure --->
26<cfset total = cf.new(runningTotal) />

Now, we write all the records from the Invoice table to the screen. Note the use of the closure to output the amount. We also call the same closure method with a different mode to output the Grand Total.

view plain print about
1<!--- Now query the Database for all the records --->
2<cfquery name="getInvoice" datasource="test">
3    SELECT InvoiceID, CreateDate, Amount
4    FROM Invoice
5</cfquery>
6
7<cfoutput>
8    <cfloop query="getInvoice">
9        <!--- output the date and the amount using the closure call --->
10        #DateFormat(CreateDate, "mm/dd/yyyy")# #total.call(mode='sum',valueToTotal=Amount)#<br />
11    </cfloop>
12<!--- Now output the value of the running total --->
13    <strong>Grand Total: #total.call(mode='Display')#</strong><br />
14</cfoutput>

Below is the result

view plain print about
112/09/06 8151
212/10/06 4885
312/11/06 9525
412/12/06 1001
512/13/06 Not Applicable
6........ snip out 95 more rows ....
7Grand Total: 384272

The grand total was calculated using the formula in the closure. It would be trivial to bind additional values to the function if we wished to complicate the formula, perhaps to add a markup value or a discount value.

This code works, you can see that. I still have plenty to learn about closures so please comment if you have thoughts or criticisms.

Update:

Sean recommends another method to create the closure which is much more succinct, using more features of the ClosureLibrary. To use:

Remove the runningTotal function definition. Then replace the <cfset total = cf.new(runningTotal) > with the following snippet:

view plain print about
1<cfset total = cf.new("if (mode IS 'display') return total; else {total = total + val(valueToTotal); return valueToTotal;}","mode,valueToTotal").bind(total=0) >

Finally, since both arguments are now required and the Closure library uses the argumentcollection, add an argument for valueToTest to the grandtotal section as follows:

Old

view plain print about
1<strong>Grand Total: #total.call(mode='Display')#</strong><br />

New

view plain print about
1<strong>Grand Total: #total.call(mode='Display', valueToTotal=0)#</strong><br />

When you run the code, the total is calculated as before. Note, The arguments are defined in the cf.new function along with binding the initial value of 'total'.

Thanks Sean!

There are no comments for this entry.

Add Comment Subscribe to Comments