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 CFUNITED 2009 (Lansdowne, VA) with: Ray Camden

Swapping ColdFusion Component Methods At Run Time

By Ben Nadel on
Tags: ColdFusion

I was in the shower this morning, thinking about Peter Bell and his LightWire code. LightWire (to dumb it down a lot) automates the passing in of dependent object during object creation / initialization. This got me thinking: we can store variables at run time but what about methods? Methods are just variables right??? Traditionally I think of methods as compile time objects. I have done things like that in Javascript where you can add functions to an object at any time because it's a really lose, awesome language, but what about ColdFusion? When it comes to methods in Components, it's not just about the method code... it's about references (ex. THIS, VARIABLES, SUPER). How does that translate at run time?

To test this, I have set up two components, Boy and Girl:

Girl.cfc

  • <cfcomponent
  • displayname="Girl"
  • output="false"
  • hint="I am a chicka.">
  •  
  • <cffunction name="Init" access="public" returntype="Girl" output="false"
  • hint="Returns an initialized Girl instance.">
  •  
  • <!--- Define arguments. --->
  • <cfargument name="Name" type="string" required="yes" />
  •  
  • <!--- Save name value. --->
  • <cfset THIS.Name = ARGUMENTS.Name />
  • <cfset VARIABLES.Name = ARGUMENTS.Name />
  •  
  • <!--- Return This reference. --->
  • <cfreturn THIS />
  • </cffunction>
  •  
  •  
  • <cffunction name="ActInappropriately" access="public" returntype="string" output="false"
  • hint="I am about to do something inappropriate.">
  •  
  • <!--- Do something crazy. --->
  • <cfreturn (
  • UCase( VARIABLES.Name ) & ": I am " & THIS.Name & " and I just flashed my boobies :)"
  • ) />
  • </cffunction>
  •  
  • </cfcomponent>

Boy.cfc

  • <cfcomponent
  • displayname="Boy"
  • output="false"
  • hint="I am a dude.">
  •  
  • <cffunction name="Init" access="public" returntype="Boy" output="false"
  • hint="Returns an initialized Boy instance.">
  •  
  • <!--- Define arguments. --->
  • <cfargument name="Name" type="string" required="yes" />
  •  
  • <!--- Save name value. --->
  • <cfset THIS.Name = ARGUMENTS.Name />
  • <cfset VARIABLES.Name = ARGUMENTS.Name />
  •  
  • <!--- Return This reference. --->
  • <cfreturn THIS />
  • </cffunction>
  •  
  •  
  • <cffunction name="ActInappropriately" access="public" returntype="string" output="false"
  • hint="I am about to do something inappropriate.">
  •  
  • <!--- Do something crazy. --->
  • <cfreturn (
  • UCase( VARIABLES.Name ) & ": I am " & THIS.Name & " and I just pooped myself :("
  • ) />
  • </cffunction>
  •  
  • </cfcomponent>

As you can see, both these objects have a way to act inappropriately (oh you guys!).

Let's start off by instantiating each of these objects and then getting them to act inappropriately (just to make sure everything is up and running):

  • <cfset Girl = CreateObject( "component", "Girl" ).Init(
  • Name = "Sarah"
  • ) />
  •  
  • <cfset Boy = CreateObject( "component", "Boy" ).Init(
  • Name = "Jeff"
  • ) />
  •  
  • <!--- Do something crazy. --->
  • #Girl.ActInappropriately()#<br />
  • <br />
  •  
  • <!--- Do something crazy. --->
  • #Boy.ActInappropriately()#<br />

This gives us the output:

SARAH: I am Sarah and I just flashed my boobies :)

JEFF: I am Jeff and I just pooped myself :(

Nice, working well. Let's just take a look at the functions for a second. Both of them refer to the THIS scope as well as the VARIABLES scope. They have the same simple value - Name was stored in both scopes. Let's see what happens when we swap the methods at run time (for the rest of these tests, assume that the Boy and Girl objects have been instantiated already):

  • <!--- Move the boy function over to the girl. --->
  • <cfset Girl.ActInappropriately = Boy.ActInappropriately />
  •  
  • <!--- Do something crazy. --->
  • #Girl.ActInappropriately()#<br />
  • <br />
  •  
  • <!--- Do something crazy. --->
  • #Boy.ActInappropriately()#<br />

This gives us the output:

SARAH: I am Sarah and I just pooped myself :(

JEFF: I am Jeff and I just pooped myself :(

As you can see, the functionality of the Boy's method was copied over to the Girl's method, however, both the THIS and the VARIABLES scope pointers translated appropriately to the new component. That's pretty cool!

Ok, that's moving a method from one component to another... but what happens if I want to create an entirely new method and store it in a component. To test this, I created this non-component-contained method, RunNaked:

  • <cffunction
  • name="RunNaked"
  • access="public"
  • returntype="string"
  • output="false"
  • hint="I am about to run around naked.">
  •  
  • <!--- Do something crazy. --->
  • <cfreturn (
  • "I am " & THIS.Name &
  • " and I just ran naked around the office :D"
  • ) />
  • </cffunction>

This method refers to the THIS scope. Outside of a component, this makes no sense. In fact, if I try to call this method as is:

  • <!--- Run naked. --->
  • #RunNaked()#

... it throws the following error:

Element NAME is undefined in THIS.

Ok, but that's not what I want to test. Let's store this method pointer into the girl's ActInappropriately method (if only this were real, right!):

  • <!--- Store a completely new function pointer. --->
  • <cfset Girl.ActInappropriately = RunNaked />
  •  
  • <!--- Do something crazy. --->
  • #Girl.ActInappropriately()#<br />
  • <br />
  •  
  • <!--- Do something crazy. --->
  • #Boy.ActInappropriately()#<br />

This gives us the output:

I am Sarah and I just ran naked around the office :D

JEFF: I am Jeff and I just pooped myself :(

Very cool! Even though the new method has no idea about what a component even was (as it was defined outside of any components), when moved over to the Girl instance, the THIS scope hooked up quite nicely.

This has been a cool little test don't you think? I have not fully looked into it, but I think this is the idea behind ColdFusion Mixins. I am not sure what I would ever use this for, but it might come in handy one day to be able to create an Empty ColdFusion Component and then dynamically give it functionality. Who knows!



Looking For A New Job?

100% of job board revenue is donated to Kiva. Loans that change livesFind out more »

Reader Comments

Another thing you can do is dynamically delete methods.

Say you have a PoleDancer class that implements a Dance() method and a Stripper class that implements a Strip() method as well as the Dance() method. Say you then have a function that operates one way if you pass it a PoleDancer and another if you pass it a Stripper (duck typing). You can call StructDelete(myStripper,"Strip") to remove that method from the Stripper, and thus the function will think that the object is a PoleDancer, not a Stripper. If you wanted to be extra sneaky:

myStripper.OldStrip = myStripper.Strip;
StructDelete(myStripper,"Strip");
PutOnAShow(myStripper); // Dance, dance, Dance()
myStripper.Strip = myStripper.OldStrip;
StructDelete(myStripper,"OldStrip");

(There are other ways to do is-a in CF, but the fastest, easiest, and most often implemented is just a StructKeyExists(obj,"Strip"), which makes this work.)

Reply to this Comment

Rick,

Well played (and thanks for getting into the spirit of the posting ;)). Now that you mention the deleting, I think I read something in the last ColdFusion Quarterly, by Ray Camden, that dynamically deletes the OnRequest() method from the Application.cfc if there is .CFC or WSDL in the URL. Can't find a link to it though.

But thanks, I totally forgot about deleting stuff. And the storing the old reference is very cool.

Reply to this Comment

Mark,

Great link! I don't know much about Ruby, but I think this is also the way things like Prototype and JQuery extend Javascript objects.

I am looking forward to working this in somewhere :)

Reply to this Comment

Hi Ben,

Nice post! The first piece is what Hal/Sean (in the CF world) describe as object based mixins. You can even cfinclude methods into a template on component instantiation for class based mixins. I've never seen just having the function sitting there all alone and adding it to a class, but it is cool to see that works too!

Reply to this Comment

I actually just quite recently updated the function libraries in the onTap framework to load methods on demand using onMissingMethod in CF8. (And am just now reading this blog entry 2yrs late). :) The libraries themselves were originally created on CF5 before there even were CFCs and until just this latest version (3.2) they were always stored in structures instead of CFCs. The dream had always been to have a collection of functions that could understand their own dependencies and be loaded as-needed instead of being required to aggressively load when the application starts. (DLL wouldn't be a terrible analogy.) But making that actually happen proved to be too difficult to manage prior to CF8 and onMissingMethod. Now they finally do, which eliminated a nice big chunk of delay when the application loads.

Reply to this Comment

@ike,

Glad you got it working. I am just starting to really appreciate the OnMissingMethod() functionality in ColdFusion 8. In OOPhoto, its really allowing me to rock some very dynamic and "smart" behaviors on my objects.

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.