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.
|