I'm trying to write a mixin in ColdFusion.
ExampleMixin.cfc:
component {
remote void function mixin(component, methodName) {
var original = component[methodName];
component[methodName] = function() {
writeOutput("Mixin!");
return original(arguments);
};
}
}
test.cfc:
component {
new ExampleMixin().mixin(this, 'foo');
remote string function foo() {
return getOutput();
}
private string function getOutput() {
return "Hello, World!";
}
}
Running foo
produces an error, Variable GETOUTPUT is undefined.
. If I comment out new ExampleMixin().mixin(this, 'foo');
, it runs fine.
It looks like when foo
is run from the wrapper, it's not running in the right context. In JavaScript, one would write foo.call(component, ...arguments)
to rectify this. Is there an equivalent in ColdFusion?
ColdFusion uses both the this
and variables
scopes for storing function
references. The reference that's used depends how the function is invoked. If
the function is invoked from a sibling, the variables
reference is used. If
the function is being invoked externally, then the this
reference is used.
The following code uses a base class to supply the mixin functionality. The
$mixin
function takes a component instance and injects all of its functions.
If there's a name collision a wrapper will call the mixin first, then the
original function. I'm generating new function names for both the original and
mixin functions so references can be set in both scopes.
This was tested on Lucee 5.2.8.50.
mixable.cfc
component {
function $mixin(obj) {
var meta = getComponentMetadata(obj);
for(var func in meta.functions) {
if(structKeyExists(this, func.name)) {
var orig = func.name & replace(createUUID(), '-', '', 'all');
var injected = func.name & replace(createUUID(), '-', '', 'all');
this[orig] = this[func.name];
variables[orig] = this[func.name];
this[injected] = obj[func.name];
variables[injected] = obj[func.name];
var wrapper = function() {
this[injected](argumentCollection=arguments);
return this[orig](argumentCollection=arguments);
};
this[func.name] = wrapper;
variables[func.name] = wrapper;
} else {
this[func.name] = obj[func.name];
return variables[func.name] = obj[func.name];
}
}
}
}
test.cfc
component extends="mixable" {
remote function foo() {
writeOutput("foo(), calling bar()<br>");
bar();
}
private function bar() {
writeOutput("bar()<br>");
}
}
mixin.cfc
component {
function foo() {
writeOutput("foo mixin, calling bar()<br>");
bar();
}
function myfunc() {
writeOutput("myfunc()<br>");
}
}
index.cfm
<cfscript>
t = new test();
t.$mixin(new mixin());
t.myfunc();
t.foo();
</cfscript>
Output
myfunc()
foo mixin, calling bar()
bar()
foo(), calling bar()
bar()