Introduction to XML in Flash
       by senocular

Trouble With XML onLoad In AS2 Classes
This is probably the biggest issue out there for people using XML in ActionScript 2.0 classes. This doesn't necessarily revolve around XML so much, but rather about dealing with callback event handlers like onLoad from within classes.

The problem is that if you define an onLoad event from within a class method, the onLoad script cannot access class properties. Example

class XMLContentLoader {
public var target_txt:TextField;
private var _xml:XML;
 
function XMLContentLoader(url:String, target:TextField){
target_txt = target;
_xml = new XML();
_xml.ignoreWhite = true;
_xml.onLoad = function(success){
if (success) target_txt.text = this.firstChild.toString();
}
_xml.load(url);
}
}

Here, the XMLContentLoader class creates instances that loads XML from a URL string and displays its firstChild (the full XML document) into a textfield saved under the target_txt property. Problem is, the onLoad can't correctly reference the target_txt property. WHY, sweet child of mine WHY!?!

The reason for this is because once you've entered that onLoad method, you are no longer in the scope of the class instance. You have now entered the scope of the XML instance. Within this scope, when attempting to access target_txt, you are attempting to access target_txt within the XML instance and not the XMLContentLoader instance. Thankfully, there are a couple of ways around this.

1. Use a local variable. If you created the event handler (onLoad) function within a class method, then that function has access to all local variables declared within that method in which its defined. If you assign the needed property reference to a local variable in that host method, it will carry into the scope of the scope of the handler giving you a valid reference.

var txt = target_txt;
_xml.onLoad = function(success){
if (success) txt.text = this.firstChild.toString();
}

For variable reference only; not safe for calling class methods.

2. Define the variable within the object receiving the handler. Since when in the onLoad the _xml instance is looking for its own target_txt and not the class instance's, what you could do is just copy that variable, keeping the same name, within the _xml instance. Then, the onLoad would correctly resolve that variable when it is referenced. Now, the flip side to this in AS 2.0 is that, at least with the XML object, that you're dealing with instances of a non-dynamic class. This means that you cannot, technically, add or access properties or methods that are not defined within its class definition. You can, however, trick Flash's compiler to ignore this by using associative array syntax to define your property, in this case, target_txt.

_xml["target_txt"] = target_txt;
_xml.onLoad = function(success){
if (success) target_txt.text = this.firstChild.toString();
}

Using _xml["target_txt"] tricks the compiler as when using [] to access variables. The compiler can't be certain of what value is within the brackets (it could be a variable that could be valid or invalid) so it ignores the reference altogether letting you get by with compiler deceiving heathenry. When in the onLoad, target_txt is correctly found because it is defined within the _xml instance. To add injury to insult, the compiler isn't smart enough to understand this change in scope either (actual running ActionScript knows the change, just not the compiler creating the SWF) so as you attempt to access target_txt in the onLoad function without [], the compiler won't complain as it still assumes that when you're using you're in the scope of the class where target_txt is a valid property.

For variable reference only; not safe for calling class methods.

3. Define a reference to the class instance within the object receiving the handler. This approach is a little like the previous only this one adds a reference to the class instance itself and not just one property. This is helpful if you're dealing with many variables or you need to pass the class instance itself in to function calls within the handler function's scope etc. Here, again, the compiler gets in the way when dealing with non-dynamic XML instances. There's an added complication, though, as when using a reference in this manner, you're not duplicating an existing class property so the compiler will know it doesn't exist where ever you use it. Thankfully, the compiler did learn one thing in its limited schooling, and that was that this-referenced properties within non-class function definitions should go ignored. So, within the onLoad, a class reference variable can be left alone by the compiler if this is used to specify that the property does in fact belong to the object in which the handler is being defined (of course this is a little contradictory to some of its other behavior, but what are you going to do). The initial class reference when defined within the XML instance will still need [] to be ignored by the compiler, however.

_xml["hostInstance"] = this;
_xml.onLoad = function(success){
if (success) this.hostInstance.target_txt.text = this.firstChild.toString();
}

Note: you could also create an undefined Object property in the class called hostInstance to prevent the need to use this. Also, if you're confident in your coding, you could also just drop the typing altogether for the XML instance preventing any of the compiler complications mentioned above.

private var _xml;

3.1. Combine 3 and 1. Use a local variable to reference the class instance. This takes away the need to a) define any extra variables within the object getting the event handler b) trick the compiler and c) define multiple variables for what ever properties you wish to reference

var host = this;
_xml.onLoad = function(success){
if (success) host.target_txt.text = this.firstChild.toString();
}

4. Use the Delegate Utility. Available as of Flash MX 2004 7.2, this is probably the most "official", though possibly also the most confusing (and less apparent) method. Delegate is a class in mx.utils available to ActionScript 2.0 that allows you to change the scope of a function call from one object to another. Basically its a wrapper for the functionality that function.call() and function.apply() provide. Delegate just makes it a little easier to intercept function definitions in one object which are to be executed within the scope of another. Here's a simple example of its use.

import mx.utils.Delegate;
var objectA:Object = {name: "object A"};
var objectB:Object = {name: "object B"};
 
function getName():String {
return this.name;
}
objectB.getName = Delegate.create(objectA, getName);
trace( objectB.getName() ); // traces "object A";

Here, the static method create is used off of the Delegate class to create a function (getName) that is assigned to object B but, when run, is run in the scope of object A. This means that any instance of 'this' in that function will refer to object A instead of object B even though it is being called from object B.

This can then be applied to the XML example were we could have the onLoad be run in the scope of the class thereby making all references to class properties valid as the call would be within the scope of the class instance and not the XML object. For the sake of simplicity, we'll make the function to be used in the onLoad a method of the class. This is not uncommon to do anyway. Here, it makes the assignment using Delegate.create() a lot easier. Here is the full modified class:

import mx.utils.Delegate;
class XMLContentLoader {
public var target_txt:TextField;
private var _xml:XML;
 
function XMLContentLoader(url:String, target:TextField){
target_txt = target;
_xml = new XML();
_xml.ignoreWhite = true;
_xml.onLoad = Delegate.create(this, onLoadEvent);
_xml.load(url);
}
 
function onLoadEvent(success:Boolean):Void {
if (success) target_txt.text = _xml.firstChild.toString();
}
}

First, in order to use Delegate, at least by its short name, import is used to bring it in from mx.utils.Delegate (otherwise, you'd have to run it as mx.utils.Delegate.create()). That happens above the class definition before anything else. Then, in the constructor you can see it being used in defining the onLoad event for the _xml XML instance. It takes two arguments, an object and a function. The object is the object in which the function is going to be run. Since we want the onLoad to run within the scope of the class, this is passed in (representing the class instance) as the object. The function is the class's own onLoadEvent function defined below. It now, when run, will correctly reference target_txt as a property of the class without any trouble. Of course, you can also see that because the scope of the onLoad method is no longer within the XML instance and this represents the class, _xml has to be used in order to access content of the XML instance and what had been loaded.

Note: If you use a method for your onLoad event that is defined as a method within the class like the onLoadEvent method above, then options 1 and 3.1 which use local variables, would not be a valid solution for the scoping problem.


 




SUPPORTERS:

kirupa.com's fast and reliable hosting provided by Media Temple.