Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at Scotch On The Rock (SOTR) 2010 (Munich) with: Christian Etbauer

Assigning Variables Within A CFLoop Condition In ColdFusion

By Ben Nadel on
Tags: ColdFusion

This morning, when I was exploring ColdFusion 9's new ArrayDelete() function, I actually stumbled upon a feature in ColdFusion that I had never tried before. This is something that I often do in Javascript, which is the only reason I actually tried it this morning without thinking. What I'm talking about is assigning a value to a variable from within the Condition of a CFLoop tag.

Often times, the Condition attribute of a CFLoop tag simply checks to see if a condition is true or false. Something like this:

  • <cfloop condition="(arrayLen( someData ) GT 5)"> .... </cfloop>

Here, our CFLoop tag will continue looping while the array, someData, has a length greater than five.

What I discovered this morning, however, was that if the Condition attribute contains a variable assignment, not only will the variable be assigned successfully, it will then also be further evaluated as a boolean:

  • <!--- Create a list of girls. --->
  • <cfset girls = "Sarah,Molly,Sarah,Joanna,Sarah" />
  •  
  • <!---
  • Keep looping over the girls and deleting the name "Sarah"
  • from the list. In the Condition attribute we are going to
  • BOTH assign a variable and test it's boolean value.
  • --->
  • <cfloop condition="targetIndex = listFind( girls, 'Sarah' )">
  •  
  • <!---
  • Delete instance of Sarah using the stored index value
  • from our condition. Note: this is not a peronal thing -
  • Sarah is a really aweosme girl.
  • --->
  • <cfset girls = listDeleteAt( girls, targetIndex ) />
  •  
  • <!--- Output the notification. --->
  • Delete Sarah at: #targetIndex#<br />
  •  
  • </cfloop>

Here, the Condition of the CFLoop tag gets the list index of a given value and assigns it to the variable, targetIndex. TargetIndex is, itself, then evaluated as a boolean condition, determining whether or not the CFloop should continue executing. And, running the above code, we get the following output:

Delete Sarah at: 1
Delete Sarah at: 2
Delete Sarah at: 3

Sweet-ass-sweet, it works like a charm!

Ironically, the same functionality is not available in a WHILE loop within CFScript. The attempt to recreate this in CFScript:

  • <cfscript>
  •  
  • // Assign girls list.
  • girls = "Sarah,Molly,Sarah,Joanna,Sarah";
  •  
  • // Keep looping while Sarah can be found.
  • while (targetIndex = listFind( girls, "Sarah" )){
  •  
  • // Delete target element.
  • girls = listDeleteAt( girls, targetIndex );
  •  
  • // Output results.
  • writeOutput( "Delete Sarah at: #targetIndex#<br />" );
  •  
  • }
  •  
  • </cfscript>

... throws the following ColdFusion compile time error:

ColdFusion was looking at the following text: = The CFML compiler was processing: A script statement beginning with while on line 41, column 9.

If you want to, you can sort of hack the CFScript-based FOR loop to act this way, but it just looks junky:

  • <cfscript>
  •  
  • // Assign girls list.
  • girls = "Sarah,Molly,Sarah,Joanna,Sarah";
  •  
  • // Keep looping while Sarah can be found.
  • for (
  • targetIndex = listFind( girls, "Sarah" ) ;
  • listFind( girls, "Sarah" ) ;
  • targetIndex = listFind( girls, "Sarah" )
  • ){
  •  
  • ...
  •  
  • }
  •  
  • </cfscript>

So, I guess the only thing appropriate to say at this point is, In your face CFScript!! Ok, ok, all joking and feelings of tag-superiority aside, I am quite happy to have found this feature available in conditional CFLoop tags.




Reader Comments

It may be just a syntax over sight. Replacing = with EQ fixes the error. However, the code using EQ will still throw an error.

"Variable TARGETINDEX is undefined."

Reply to this Comment

@Steve,

Right, because EQ turns it from an assignment into a comparison. That will work, but because there is no assignment, the targetIndex is no longer valid.

Reply to this Comment

Apparently Adobe dropped the ball with "everything you can do with tags you can do with script", eh? Of course, I'd probably "fix" that code by adding the second equals sign (to make an equality instead of an assignment) if I saw it.

Here's a better way with CFSCRIPT:

while (true) {
targetIndex = listFind(girls, "Sarah");
if (targetIndex EQ 0) {
break;
}
girls = listDeleteAt(girls, targetIndex);
}

Reply to this Comment

@Ben

Ahh, I gotcha. I really haven't used the condition attribute much, maybe only a handful of times. Good to know though!

Reply to this Comment

@Barney,

Yeah, that is definitely a better way. I was just trying to translate the variable-assignment-as-part-of-operator functionality.

The biggest discrepancy that I'm seeing in CFScript is CFHeader and CFContent. I cannot find any information on these.

@Steve,

Yeah, condition is the most popular loop feature. Heck, have the time I use it, my condition is "True" as in Barney's example, although mine is tag-based.

Reply to this Comment

Did some other tests.

Type 0

<cfloop condition="(x = 0)"></cfloop>

This doesn't work. Error: "Invalid CFML construct found on line 1 at column 5. ColdFusion was looking at the following text: ="

This seems different than normal expressions which use parentheses as precedence.

Type 1

<cfloop condition="x = 0"></cfloop>

This works, but maybe not as one might expect. The loop never runs. An assignment loop takes the value of the variable as the condition value.

This is in common with C, C++, and Perl. But not Python or Ruby.

No biggie. Until you see type 3.

Type 2

<cfloop condition="x = reFind(p, s)"></cfloop>

This works great! Thanks CF team.

Type 3

<cfloop condition="x = reFind(p, s, true)"></cfloop>

This doesn't work. Error: "Cannot convert the value of type class coldfusion.runtime.Struct to a boolean".

Similar to type 1. IMHO, Shouldn't it be anything but 0/false/undefined evaluates as if it were true?

This commonly works in other languages.

Another strike against the CF expression evaluator.

Type 4

<cfloop condition="((x = 1) and (x gt 0))"></cfloop

This doesn't work. Error: "Invalid CFML construct found on line 1 at column 5. ColdFusion was looking at the following text: ="

Similar to type 0.

<cfloop condition="x = 1 and x gt 0">

This doesn't work. Error. "Variable X is undefined."

Shouldn't x have been set by the first clause?

This commonly works in other languages. It's called precedence.

Another strike against the CF expression evaluator.

Type 5

<cfloop condition="((structKeyExists(url, ""x"")) and (x gt 0))"></cfloop>

This works great! Thanks CF team.

But how come precedence works for this type of expression, but not for assignment?

Thoughts?

Reply to this Comment

@Alex,

I think what it comes down to is that the Condition value is evaluated as a ColdFusion expression (perhaps using the Evaluate() method or something similar). When it does that, it runs the code and then, I guess, uses the result as a Boolean.

Some of your Conditions throw errors because they are not valid ColdFusion statements. If you tried to run:

<cfscript> (x=0); </cfscript>

... as a standard CF statement, it would throw the same error - not valid syntax.

Also, because the condition needs to be evaluated as a Boolean, you have to get a full understanding of what is a "Truthy" value in ColdFusion, which is limited to non-zero numeric values (or things that can be implicitly converted to numbers), true keywords, and YES. Anything else probably cannot be converted to a boolean, which is why the reFind() that returns an array fails.

Reply to this Comment

@Ben,

> (x=0)
> not valid syntax

I understand.

Then expression syntax is different for assignment expressions than it is for comparison expressions.

Because these work fine:

assignment: <cfloop condition="x = 0"></cfloop>
comparison: <cfloop condition="(x eq 0)"></cfloop>
comparison: <cfloop condition="((x eq 0) or (x eq 1))"></cfloop>

Reply to this Comment

@Alex,

Yeah, exactly - some small differences. I actually prefer to use parenthesis around my conditions, so it's a shame that it throws an error :(

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.