The Flash 7.2 updater adds the mx.utils.Delegate class to the standard ActionScript library. The class is designed to assist in a variety of things, including scope issues with regard to event handler methods of class members. For example, many people have encountered the issue of trying to use the onLoad( ) or onData( ) event handler methods of a LoadVars or XML object within a class. The Delegate class helps with that. Consider the following simple scenario.
class SomeClass {
private var _sProperty:String;
public function SomeClass(sURL:String) {
var lvInstance:LoadVars = new LoadVars();
lvInstance.onData = function(sText:String):Void {
// The following won't work because _sProperty is not defined within the lvInstance scope.
_sProperty = sText;
};
lvInstance.load(sURL);
}
}
There are several ways of working around that issue. However, the Delegate class provides one of the better ways of dealing with it. You can use the static create( ) method to return a proxy function that will, in turn, call another function (or method) using a specific scope.
class SomeClass {
private var _sProperty:String;
public function SomeClass(sURL:String) {
var lvInstance:LoadVars = new LoadVars();
// Tell Flash to call the assignText( ) method using this as the scope.
lvInstance.onData = mx.utils.Delegate.create(this, assignText);
lvInstance.load(sURL);
}
// Since the assignText( ) method is called with the this scope, _sProperty is defined.
private function assignText(sText:String):Void {
_sProperty = sText;
}
}
That's fairly helpful. However, in some cases the Delegate class doesn't quite do what you would want. Specifically, you cannot pass additional parameter to the method that is called by proxy. In the preceding example that's okay. You don't need to pass any additional parameters. However, there are some more complex scenarios in which it is, if not necessary, at least very helpful to be able to pass additional parameters. Some workarounds have been posited that facilitate that in a sense. Those workarounds generally involve adding custom properties to the proxy function object, and then referencing them from within the method called by proxy using the arguments.caller reference. However, while that works, it's not a very elegant solution. The following shows an example.
class SomeClass {
private var _sProperty:String;
public function SomeClass(sURL:String) {
var lvInstance:LoadVars = new LoadVars();
// Make the function.
var fDelegateFunction:Function = mx.utils.Delegate.create(this, assignText);
// Assign a custom property to the function object.
fDelegateFunction.customProperty = "some property value";
// Assign the function to the onData event handler.
lvInstance.onData = fDelegateFunction;
lvInstance.load(sURL);
}
private function assignText(sText:String):Void {
_sProperty = sText;
// Use arguments.caller to reference the proxy function to which the property was added.
trace(arguments.caller.customProperty);
}
}
It would be much more useful if it was possible to simply specify additional parameters in the parameter list when calling create( ). The Delegate class doesn't allow that. But the ascb.util.Proxy class does. So the preceding example can be rewritten as follows.
class SomeClass {
private var _sProperty:String;
public function SomeClass(sURL:String) {
var lvInstance:LoadVars = new LoadVars();
// The syntax for ascb.utils.Proxy.create( ) is similar to that of Delegate.create( ).
// However, you can also specify additional parameters to pass along to the method.
lvInstance.onData = ascb.utils.Proxy.create(this, assignText, "some property value");
lvInstance.load(sURL);
}
private function assignText(sText:String, sAnotherParameter:String):Void {
_sProperty = sText;
// Much simpler.
trace(sAnotherParameter);
}
}
Of course the preceding example is intentionally simplified to illustrate how the class works in theory, but it's not a very practical example. So next, take a look at the following class that illustrates how the Proxy class can be useful in a slightly more practical way. The class draws a ten by ten grid of squares with random numeric labels. Each square can be clicked, and the corresponding label is then displayed in a text field.
class SimpleProxyExample {
private var _mParent:MovieClip;
private var _tSelected:TextField;
public function SimpleProxyExample(mParent:MovieClip) {
_mParent = mParent;
var nX:Number = 0;
var nY:Number = 0;
var mButton:MovieClip;
for(var i:Number = 0; i < 10; i++) {
for(var j:Number = 0; j < 10; j++) {
mButton = drawButton();
mButton._x = nX;
mButton._y = nY;
mButton.onRelease = ascb.util.Proxy.create(this, select, mButton.tLabel.text);
nX += 25;
}
nX = 0;
nY += 25;
}
_mParent.createTextField("____TextFieldInstance", _mParent.getNextHighestDepth(), 0, nY, 100, 20);
_tSelected = _mParent.____TextFieldInstance;
_tSelected.border = true;
}
private function drawButton():MovieClip {
var nDepth:Number = _mParent.getNextHighestDepth();
var mButton:MovieClip = _mParent.createEmptyMovieClip("____MovieClipButtonInstance" + nDepth, nDepth);
mButton.lineStyle(0, 0, 0);
mButton.beginFill(0xFFFF00, 100);
mButton.lineTo(20, 0);
mButton.lineTo(20, 20);
mButton.lineTo(0, 20);
mButton.lineTo(0, 0);
mButton.endFill();
mButton.createTextField("tLabel", 1, 0, 0, 20, 20);
mButton.tLabel.selectable = false;
mButton.tLabel.text = Math.round(Math.random() * 50);
return mButton;
}
private function select(sLabel:String):Void {
_tSelected.text = "selected: " + sLabel;
}
}
You can download the Proxy class at www.person13.com/downloads/ascb_util_Proxy.zip.
The class is part of ASCBLibrary, most of which is
discussed in greater detail in the forthcoming second edition of the ActionScript Cookbook. The actual Proxy class code is
fairly simple, as you can see.
class ascb.util.Proxy {
public static function create(oTarget:Object, fFunction:Function):Function {
// Create an array of the extra parameters
passed to the method.
// through every element of the arguments array starting with index 2,
// and add the element to the aParameters array.
var aParameters:Array = new Array();
for(var i:Number = 2; i < arguments.length; i++) {
aParameters[i - 2] = arguments[i];
}
// Create a new function that will be the proxy function.
var fProxy:Function = function():Void {
// The actual parameters to pass along to the method called by proxy
// should be a concatenation of the arguments array of this function
// and the aParameters array.
var aActualParameters:Array = arguments.concat(aParameters);
// When the proxy function is called, use the apply( ) method to call
// the method that is supposed to get called by proxy. The apply( )
// method allows you to specify a different scope (oTarget) and pass
// the parameters as an array.
fFunction.apply(oTarget, aActualParameters);
};
// Return the proxy function.
return fProxy;
}
}