Using ColdFusion Method Pointers Without Updating ColdFusion Component Methods

Posted October 12, 2006 at 6:02 PM

Tags: ColdFusion

The other day, I talked about swapping methods in and out of ColdFusion components at run time. I did this by altering the methods defined in the component object:

 Launch code in new window » Download code as text file »

  • <!--- Add new method to component. --->
  • <cfset objComp.NewMethod = NewMethodPointer />

... and then was able to call the new method directly, as if it were (and it was) a method of the component itself:

 Launch code in new window » Download code as text file »

  • <!--- Call new method. --->
  • <cfset objComp.NewMethod() />

This was pretty cool, and it turns out, this is the idea behind ColdFusion Mixins which I had only heard about but never really researched.

What's neat about this is that the method auto wires itself into the appropriate scopes. Meaning, if the method refers to the THIS or the VARIABLES scope, then those references automatically refer to the THIS and VARIABLES scope of the methods new parent scope (ie. the component itself). Nicely done ColdFusion!

To take this one step in another direction, I wanted to see what would happen if you treated methods as behaviors, not as children of the component. To test this, I have created my Stripper object:

 Launch code in new window » Download code as text file »

  • <cfcomponent
  • displayname="Stripper"
  • output="false"
  • hint="I am a stripper and I am crazy hot!">
  •  
  • <!---
  • Run the pseudo constructor to set up default
  • data structures.
  • --->
  • <cfscript>
  •  
  • // Set up default public properties.
  • VARIABLES.Actions = ArrayNew( 1 );
  •  
  • // Set up clothing properties.
  • VARIABLES.IsWearingShirt = true;
  • VARIABLES.IsWearingPants = true;
  • VARIABLES.IsWearingBra = true;
  • VARIABLES.IsWearingPanties = true;
  •  
  • </cfscript>
  •  
  •  
  • <cffunction name="Init" access="public" returntype="Stripper" output="no"
  • hint="Returns an initialized Stripper instance.">
  •  
  • <!--- Return This reference. --->
  • <cfreturn THIS />
  • </cffunction>
  •  
  •  
  • <cffunction name="AddAction" access="public" returntype="void" output="false"
  • hint="Adds an action for the strip routine.">
  •  
  • <!--- Define arguments. --->
  • <cfargument name="Action" type="any" required="true" />
  •  
  • <!--- Add to the actions array. --->
  • <cfset ArrayAppend( VARIABLES.Actions, ARGUMENTS.Action ) />
  •  
  • <!--- Return out. --->
  • <cfreturn />
  • </cffunction>
  •  
  •  
  • <cffunction name="Strip" access="public" returntype="string" output="false"
  • hint="I run through the actions you tell me to and then return my status.">
  •  
  • <!--- Define the local scope. --->
  • <cfset var LOCAL = StructNew() />
  •  
  • <!--- Loop over actions and perform. --->
  • <cfloop index="LOCAL.ActionIndex" from="1" to="#ArrayLen( VARIABLES.Actions )#" step="1">
  •  
  • <!--- Get method pointer. --->
  • <cfset LOCAL.Action = VARIABLES.Actions[ LOCAL.ActionIndex ] />
  •  
  • <!--- Perform action. --->
  • <cfset LOCAL.Action() />
  •  
  • </cfloop>
  •  
  •  
  • <!--- Get new line. --->
  • <cfset LOCAL.NewLine = "<br />" />
  •  
  • <!--- Build the string buffer for our repsonse. --->
  • <cfset LOCAL.Response = CreateObject( "java", "java.lang.StringBuffer" ).Init( "" ) />
  •  
  • <!--- Add the physical checks. --->
  • <cfset LOCAL.Response.Append(
  • "Wearing Shirt: " & YesNoFormat( VARIABLES.IsWearingShirt ) & LOCAL.NewLine
  • ) />
  •  
  • <cfset LOCAL.Response.Append(
  • "Wearing Pants: " & YesNoFormat( VARIABLES.IsWearingPants ) & LOCAL.NewLine
  • ) />
  •  
  • <cfset LOCAL.Response.Append(
  • "Wearing Bra: " & YesNoFormat( VARIABLES.IsWearingBra ) & LOCAL.NewLine
  • ) />
  •  
  • <cfset LOCAL.Response.Append(
  • "Wearing Panties: " & YesNoFormat( VARIABLES.IsWearingPanties ) & LOCAL.NewLine
  • ) />
  •  
  •  
  • <!--- Return the response. --->
  • <cfreturn LOCAL.Response.ToString() />
  • </cffunction>
  •  
  • </cfcomponent>

As you can see, the Stripper keeps track of what articles of clothing she is wearing via several VARIABLES-scoped values. There is also a private array, Actions, which will hold the action the Stripper must perform during her strip tease. We can add actions to this array at any time and then ask the stripper to strip. Once she does this, she returns the status of her clothing. To demonstrate this, let's create a stripper and ask her to strip, having not provided any actions:

 Launch code in new window » Download code as text file »

  • <!--- Create the stripper. --->
  • <cfset Stripper = CreateObject(
  • "component",
  • "Stripper"
  • ).Init()
  • />
  •  
  • <!--- Tell stripper to strip. --->
  • #Stripper.Strip()#

This gives us the following output:

Wearing Shirt: Yes
Wearing Pants: Yes
Wearing Bra: Yes
Wearing Panties: Yes

Since no actions were assigned, no clothing was removed (oh well).

Now, let's create a few actions that we can send her way.

 Launch code in new window » Download code as text file »

  • <cffunction
  • name="RemoveShirt"
  • access="public"
  • returntype="void"
  • output="false"
  • hint="I remove my shirt... that's kind of hot.">
  •  
  • <!--- Remove shirt. --->
  • <cfset VARIABLES.IsWearingShirt = false />
  •  
  • <!--- Return out. --->
  • <cfreturn />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="RemovePants"
  • access="public"
  • returntype="void"
  • output="false"
  • hint="I remove my pants... now we're getting somewhere.">
  •  
  • <!--- Remove pants. --->
  • <cfset VARIABLES.IsWearingPants = false />
  •  
  • <!--- Return out. --->
  • <cfreturn />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="RemoveBra"
  • access="public"
  • returntype="void"
  • output="false"
  • hint="I remove my bra... is it cold in here???">
  •  
  • <!--- Remove bra. --->
  • <cfset VARIABLES.IsWearingBra = false />
  •  
  • <!--- Return out. --->
  • <cfreturn />
  • </cffunction>

As you can see, all of these functions are referencing the VARIABLES scope. Called on their own, they will not work, as free-floating functions do NOT have VARIABLES scopes. We could set these as built-in methods of the Stripper component, but that is what we have already tested. Let's try adding some of these to the Stripper's Action array and then asking her to strip again:

 Launch code in new window » Download code as text file »

  • <!--- Add stipper actions. --->
  • <cfset Stripper.AddAction( RemoveShirt ) />
  • <cfset Stripper.AddAction( RemovePants ) />
  • <cfset Stripper.AddAction( RemoveBra ) />
  •  
  • <!--- Tell stripper to strip. --->
  • #Stripper.Strip()#

This gives us the following output:

Wearing Shirt: No
Wearing Pants: No
Wearing Bra: No
Wearing Panties: Yes

Awesome! As you can see, even though the methods were not "part" of the component, the scopes transferred over exactly how we would want them to. And, the structure of the ColdFusion component was not changed at all. Let's take a close look at how these are called:

 Launch code in new window » Download code as text file »

  • <!--- Loop over actions and perform. --->
  • <cfloop
  • index="LOCAL.ActionIndex"
  • from="1"
  • to="#ArrayLen( VARIABLES.Actions )#"
  • step="1">
  •  
  • <!--- Get method pointer. --->
  • <cfset LOCAL.Action = VARIABLES.Actions[ LOCAL.ActionIndex ] />
  •  
  • <!--- Perform action. --->
  • <cfset LOCAL.Action() />
  •  
  • </cfloop>

As you can see, the Strip method just loops over the actions array, get the method point, and then executes it. Pretty cool. ColdFusion, so dynamic, so freakin' cool!

Download Code Snippet ZIP File

Post Comment  |  Ask Ben  |  Other Searches  |  Print Page





Reader Comments

Mar 14, 2007 at 11:25 AM // reply »
100 Comments

Minor nit-pick: free-standing functions *do* have a VARIABLES scope - it refers to the page in which the function is called. A function's VARIABLES scope is dynamically bound in the context in which it is called which is why it binds to the CFC's VARIABLES scope after being injected into the CFC and called as part of object.method()...


Mar 14, 2007 at 11:28 AM // reply »
7,512 Comments

Good point. The "context sensitive" Variables binding is pretty cool. It's like the THIS scope in Javascript.


Apr 20, 2008 at 3:27 AM // reply »
9 Comments

Ben,

This has to be the most interesting way to learn CFC's I have seen so far ... I actually got ... excited

about the component ...

you know, the object ...

yeah.

E


Apr 20, 2008 at 1:29 PM // reply »
7,512 Comments

@Edward,

Glad you like. Just trying to help people learn ;)


Dec 17, 2008 at 11:59 PM // reply »
2 Comments

I know this is an old post, but I thought I'd write up a quick thank you to Ben as stumbling into this post via google saved a lot of f**king about.

As a side note, you can also structAppend( baseComponent, helperComponent) if you have a helper class that you need to attach and you can't/don't want to use extends.

Great when using object factories.


Dec 18, 2008 at 8:18 AM // reply »
7,512 Comments

@Gabriel,

Using StructAppend() with components! I have never even thought about that. Very cool idea.


Dec 18, 2008 at 7:46 PM // reply »
2 Comments

See the following scenario (code deliberately abbreviated);

Code:
<cfcomponent>
<cffunction name="init" returntype="myClass">
<cfscript>
// init params, etc
</cfscript>
</cffunction>
</cfcomponent>

On the calling page:
<cfset myClass=createObject('component','myClass').init() />
<cfset myClass.myMethod() />

Obviously that fails... Imagine that as per the example we have created myMethod elsewhere, we append it to myClass
<cfset myClass.myMethod = myMethod />

Now this works...
<cfset myClass.myMethod() />

However, if for any reason you need to use that method INSIDE the class?
<cfcomponent>
<cffunction name="init" returntype="myClass">
<cfscript>
// init params, etc
</cfscript>
</cffunction>

<cffunction name="myInbuiltFunction" returntype="void">
<cfset myMethod() />
</cffunction>
</cfcomponent>

<cfset myClass.myInbuiltFunction() />

Fails because CF can't find the mixed in function.

Why would you want to do this? I have helper functions that I didn't want to have to write into EVERY object of a certain type, I couldn't use extends due to multiple inheritance limitations, blah, blah, blah...

There seems to be 2 main solutions to this problem, which have been blogged about by Joe Rinehart (http://firemoss.net/post.cfm/ColdFusion-Mixins--For-now-Ill-CFInclude) and Sean Corfield (http://corfield.org/blog/index.cfm/do/blog.entry/entry/Mixins) amongst others... using cfinclude and mixins.

Both have pros and cons and I'm not going to bloat this comment any further by debating each, I just wanted to highlight this particular issue and provide links to the other posts incase anyone else stumbles over this thread in search of answers.

(And I'm in the mixin camp, if anyone cares to know.)


Mar 23, 2009 at 7:59 PM // reply »
1 Comments

For you a woman is nothing but an "object"... :)


Mar 23, 2009 at 8:01 PM // reply »
7,512 Comments

@Byron,

He he :)


Post Comment  |  Ask Ben

Recent Blog Comments
Mar 16, 2010 at 11:34 AM
Managing ColdFusion Sessions In A ColdFusion Builder Extension
"It's all just good conversation - I certainly am not that well versed in Builder yet; heck, I wrote this blog post AS I was exploring the concept ;)" Heh true - didn't mean to imply you shouldn't p ... read »
Mar 16, 2010 at 11:31 AM
Managing ColdFusion Sessions In A ColdFusion Builder Extension
@Raymond, It's all just good conversation - I certainly am not that well versed in Builder yet; heck, I wrote this blog post AS I was exploring the concept ;) I tried dumping out the CGI; you act ... read »
Mar 16, 2010 at 11:24 AM
Managing ColdFusion Sessions In A ColdFusion Builder Extension
FYI, you can also consider using a "one page app", ala Terry's Flex based "Builder Stats" - or just using jQuery for a rich app. ... read »
Mar 16, 2010 at 11:22 AM
Managing ColdFusion Sessions In A ColdFusion Builder Extension
"Once you're in the "web" work flow, I don't think you should need to modify any links? At that point, I think the user is basically in a stand-alone browser type of situation (theory) where cookies ... read »
Mar 16, 2010 at 11:19 AM
Managing ColdFusion Sessions In A ColdFusion Builder Extension
@Raymond, Once you're in the "web" work flow, I don't think you should need to modify any links? At that point, I think the user is basically in a stand-alone browser type of situation (theory) whe ... read »
Mar 16, 2010 at 11:16 AM
Managing ColdFusion Sessions In A ColdFusion Builder Extension
URLSessionFormat does a check to see if cookies are enabled. If not, it auto-adds the url token to the end. This _should_ work all the time, but I've seen it fail. Seriously though - if your exten ... read »
Mar 16, 2010 at 11:15 AM
Managing ColdFusion Sessions In A ColdFusion Builder Extension
@Raymond, Also, I should probably clarify that this really only works IF you intend to switch from XML to HTML after the first request. I am not sure how cross-XML requests (multi-step XML wizard) ... read »
Mar 16, 2010 at 11:11 AM
Managing ColdFusion Sessions In A ColdFusion Builder Extension
@Raymond, As far as the UrlSessionFormat(), I am not sure I follow what you mean? Are you saying that you are adding the session tokens into the target URLs in your response XML? Or in the HTML mar ... read »