Thursday, October 17, 2002

kwfu: ['UI','architecture','slashdot']

An interesting thread about UI architecture popped up on slashdot today. Qute a few of the responses don't answer the question, but a lot of them should be interesting to people building complex systems with CFMX and Flash.

Spike

Wednesday, October 16, 2002

kwfu: ['CF','Link','advanced']
Stumbled across this page on the Macromedia website today. Very useful set of articles on Advanced ColdFusion development all in the same place.

Spike
kwfu: ['super','cfc','extends']

Continuing the cfc theme of the last couple of weeks, here's a little trick that might be useful to some people.

Suppose we have a cfc called base.cfc that has a method called init(). The purpose of init() is to set up some initial data and parameters for the component. The use of init() to initialize an object is common in the Java world, so it makes some sort of sense to follow the same idea in components.

Now, suppose we have a cfc called extends.cfc that extends base.cfc. Extends.cfc also has a method called init() to initialize it's data. This could cause a problem because extends.init() will override the init() function declared in base.cfc. In the Java world, we would use the super keyword to access the init() method in the parent component. Unfortunately the super keyword doesn't work with components so there isn't an easy way to call a method that has been overridden by an extending component.

The little trick that I'm about to show you relies on the fact that you can attach methods to a component after it has been instantiated. If the data in the un-named scope is put into a structure, you can easily access all the data in that structure as long as you know what it is called. For the purposes of this example I'm going to use 'protected' as the name of my structure.

Take the following code:

base.cfc
<cfcomponent hint="base component">


<cffunction name="init">
<!--- Create a structure called protected in the un-named scope.
This structure will act as a pseudo scope for component data --->
<cfset protected = structNew()>
<!--- Add some data to the protected structure --->
<cfset protected.a = "value of a">
<cfset protected.b = "value of b">
</cffunction>


</cfcomponent>

Extends.cfc
<cfcomponent hint="extending component" extends="base">



<!--- This function is used to get the data from the super object.
It could be attached to any component instance to access the data in the 'protected' scope --->
<cffunction name="getProtected" access="private">
<cfif isDefined('protected')>
<cfreturn protected>
</cfif>
</cffunction>

<cffunction name="init">
<!--- Create a structure called protected in the un-named scope.
This structure will act as a pseudo scope for component data --->
<cfset protected = structNew()>

<!--- Create an object that points to the ancestor of this component --->
<cfset super = createObject('component','base')>

<!--- Attach the getProtected() method to our super component --->
<cfset super.getProtected = getProtected>

<!--- Fire the init() method of the ancestor --->
<cfset super.init()>

<!--- Make the private data from the ancestor component available to this component --->
<cfset super.protected = super.getProtected()>

<!--- Add the protected data from the ancestor to this component's protected structure --->
<cfset structAppend(protected,super.protected,false)>

<!--- Add some data to the local protected structure --->
<cfset protected.c = "value of c">
<cfset protected.d = "value of d">
</cffunction>

<!--- This function returns the protected data from the current component instance --->
<cffunction name="getAll">
<cfreturn protected>
</cffunction>

</cfcomponent>

invoke.cfm
<cfset obj = createObject('component','extends')>
<cfset obj.init()>
<cfdump var="#obj.getAll()#">

Now we've managed to create the functionality of a super.init() in a CFC.

The caveat to this is that we're not really duplicating what you get with the super keyword in Java. The important difference is that in Java we wouldn't be creating a separate object instance to access the method declared in the parent, the method would be running from within the context of the extending component. This is important if the method relies in any way on the data that exists in the instance.

For example:

You have a generic shopping cart component called cart.cfc that has a method called sum() that calculates the total cost for the items in a cart. Say bargain.cfc extends cart.cfc and has a method called sum() that calls the sum() method in the cart.cfc, then applies a discount of x% to the result. If the cart items are stored in the un-named scope of bargain.cfc, the sum() method in cart.cfc will not have access to them, and so will most likely return ZERO as the total cost for the items in the cart!

Clearly this is not desireable, so although we can get some of the way to emulating super in Java, it doesn't get us all the way there.

Spike

Monday, October 14, 2002

kwfu: ['cfc']
A little update to the 'this' vs 'un-named' scope discussion from last week.

Sean Corfield posted this nice little snippet on a mailing list:

test.cfc:
<cfcomponent>
<cfset bar = 42/>
</cfcomponent>

invoke.cfm:
<cfscript>
function foo() {
return bar; // not this.bar!
}
// create an instance of the component
x = createObject("component","test");
// attach the function to the component
x.abc = foo;
// Create a variable to hold the return value of the attached method
y = x.abc();
</cfscript>
<--- Output the results --->
<cfoutput>
<cfdump label="x" var="#x#"/>
y = x.abc() returned #y#
</cfoutput>


"You can add UDFs to component instances and manipulate them
however you want. Because CFMX resolves names at runtime - late binding
- not at compile time, you can add code after the component has been
written that does 'Bad Things(tm)' with the component...

This is true of any language that does dynamic binding of variables
(until the compiler is specified to add binding restrictions at
compile-time)."

It turns out that not only can you attach a method to a component to access the variables in the un-named scope. You can also call methods with an access attribute of private...

test.cfc
<cfcomponent>
<cfset bar = 42/>
<--- Create a function that is private --- >
<cffunction name="hideme" access="private">
<cfreturn "You're kidding, right?">
</cffunction>
</cfcomponent>



invoke.cfm
<cfscript>

function foo() {
return bar; // not this.bar!
}
// Create a function that calls the hideme() method.
function bar() {
return hideme();
}

x = createObject("component","test");
// Attach the method to return a hidden property
x.abc = foo;
// Attach the method to call a hidden function
x.def = bar;
// Call the functions we just attached and set variables equal to their return values.
y = x.abc();
z = x.def();

</cfscript>


<cfdump label="x" var="#x#"/>

<cfoutput>
y = x.abc() returned #y#<br>
z = x.def() returned #z#<br>
</cfoutput>


Interesting stuff.

Spike