Monday, January 10, 2011

CF: Explicit "undefined" in ColdFusion 9 results in Bug when using CustomTag Attribute collections

Adobe ColdFusion 9 introduced many new enhancements but as with any major release there are new behaviors and new problems galore.
This particular bug I encountered deals with a change in behavior of component function processing. I encountered this while migrating an application from CF8 to CF9.
In previous releases of ColdFusion an Argument that was not passed would not exist in the arguments scope. With ColdFusion 9, an argument is always created even if not passed if it is part of the arguments declaration in your function.

For example this simple function:


<cffunction name="fTwo">
<cfargument name="argA" default="1">
<cfargument name="argB" required="no">
<cfreturn arguments>

</cffunction>


a dump of this function will return:

1: <cfdump var="#fTwo()#">
2:



ARGA 1
ARGB undefined

Rather than just

ARGA 1

Thus ColdFusion 9 is introducing a new state in the variables, the Explicit "undefined".
Since this is a new state all function working with CF objects/ i.e. variables will also need to be aware of it. And most are and, thus, little problem.

However, if you introduce some slight alterations, e.g. call a custom tag from a component, this system fails.
You will get errors as the IsDefined() function will identify something as defined while it is not.

Let's introduce a simple custom tag (CT9Test) with the following 6 lines of code:


1: <cfdump var="#Attributes#">
2:
<cfif IsDefined("Attributes.ArgB")>
3: Attributes B is defined
4:
<cfelse>
5: Attributes B is NOT Defined
6:
</cfif>
7:
8:



First let's call this custom tag from the function like so:

1: <cffunction name="fTwo">
2:
<cfargument name="argA" default="1">
3:
<cfargument name="argB" required="no">
4:
<cf_CT9Test attributeCollection = "#Arguments#">
5:
</cffunction>
6:



Nope. This is still good. No problem here. But, let's go ahead and break ColdFusion:

1: <cffunction name="fThree">
2:
<cfargument name="argA" default="1">
3:
<cfargument name="argB" required="no">
4:
<cf_CT9Test anotherVar="something" attributeCollection = "#Arguments#">
5:
</cffunction>
6:


You see the difference?
We are simply adding another parameter to be passed to the custom tag in addition to the attribute collection received from the function arguments.
In the above case, the attributes.argB all of a sudden becomes defined. But since it is explicitly "undefined" using it will throw weird errors.
Something like:
if (IsDefined("attributes.argB") ) calc=attributes.argB + 1;
will fail.

The workaround to this is to go back to scenario one and not use any additional parameters when calling your custom tags and using attributeCollection. Package all parameters into one structure, e.g.

1: <cffunction name="fFour">
2:
<cfargument name="argA" default="1">
3:
<cfargument name="argB" required="no">
4:
<cfset arguments.anotherVar="something">
5:
<cf_CT9Test attributeCollection = "#Arguments#">
6:
</cffunction>
7:


Now that you know this. Happy migrating.
-B