Macromedia Flash 8 ActionScript: Training from the Source
There are many classes in ActionScript that use the callback event handler method style. For example, MovieClip uses callback methods such as onPress() and onRelease() to handle mouse events, and LoadVars uses onLoad() and onData() to get notifications when the data has loaded. When you use an instance of such a class as a property of a custom class, it is quite likely that you'll run into scope issues. Let's look at an example. Consider the following class that defines a LoadVars object as a property. It adds an onData() method to the LoadVars instance so that it can get a notification when the data loads. When the data loads, it attempts to assign the text to a private property of the class. class Example { private var _textLoader:LoadVars; private var _text:String; public function get text():String { return _text; } public function Example() { _textLoader = new LoadVars(); _textLoader.onData = function(data:String):Void { _text = data; }; _textLoader.load("file.txt"); } }
Although the preceding code might seem logical, it does not work as intended. When the data loads, Macromedia Flash will call the onData() method of the LoadVars property. However, within the onData() method it attempts to reference _text, which is declared as a property of the Example class. Because the onData() method is a method of _textLoader, it is scoped to _textLoader, not to the instance of the Example class. That means that when onData() gets called, it doesn't know what _text is, so it won't correctly assign the data to the private property of the Example instance. The most obvious solution is to define the onData() method as a method of the Example class. The following code illustrates how that might look: class Example { private var _textLoader:LoadVars; private var _text:String; public function get text():String { return _text; } public function Example() { _textLoader = new LoadVars(); _textLoader.onData = onText; _textLoader.load("file.txt"); } private function onText(data:String):Void { _text = data; } }
Although the preceding rewrite appears to be different from the first example, it works in exactly the same way. ActionScript 2.0 is a prototype-based language, so when a function reference is assigned to a callback for an object, the function is always called as a method of the object to which the reference was assigned. Even though onText() is defined as a method of Example, when it gets called as a callback of _textLoader it is called as a method of that object, and the same scope issue exists as previously. The solution is to use a class called mx.utils.Delegate. The Delegate class defines a static method, create(), which returns a function that will call a specified function with the correct scope. The create() method requires two parameters: the object to which you want the function call scoped and the function. The following code rewrites the Example class with the Delegate.create() method so that when onText() is called, it is called as a method of the Example instance: import mx.utils.Delegate; class Example { private var _textLoader:LoadVars; private var _text:String; public function get text():String { return _text; } public function Example() { _textLoader = new LoadVars(); _textLoader.onData = Delegate.create(this, onText); _textLoader.load("file.txt"); } private function onText(data:String):Void { _text = data; } }
You can use the Delegate.create() method to correct scope issues with any callback method. For example, you can assign the return value from Delegate.create() to the onPress() method of a movie clip. You can even use Delegate.create() when registering listeners to component instances via the addEventListener() method. In this task you'll build a text loader component that uses Delegate.create().
|