As cool as I think E4X is, there are a few things about it that can throw you off when you’re getting started (they certainly took me a while to get used to). I thought I would pass on these tips.
resultFormat="e4x"
on your HTTPServiceThe default resultFormat
for an <mx:HTTPService>
is "object"
. When you
are retrieving XML data, "object"
probably isn’t what you want. And you
definitely don’t want "xml"
– that is a legacy format.
The difference is in what comes back in the lastResult
. If you use the
default of "object"
, then the lastResult
is an Object with regular
ActionScript properties on it. (Actually it’s an ObjectProxy, but that’s an
implementation detail.) If you use "e4x"
, then the lastResult
is an XML
object which you can easily parse with E4X expressions.
So when would you want "object"
? Well, I’m not really sure. I always use
"e4x"
.
Say you have an <mx:HTTPService>
called myService
, with
resultFormat="e4x"
, and a call to it returns this document from your server:
<?xml version="1.0"?>
<people>
<person>mike</person>
<person>sho</person>
<person>nj</person>
</people>
The data is now in myService.lastResult
(or, if you are in the result
event
handler, the data is in event.result
.) How do you get an XMLList for all of
the
It is a very common mistake to think that your E4X expression needs to specify
the top-level
var personNodes:XMLList = myService.lastResult.people.person; // wrong
That doesn’t work. You should think of the myService.lastResult
as being
synonymous with the top-level node. So the way you get a list of the
var personNodes:XMLList = myService.lastResult.person; // right
for each
, not for
ECMAScript has had for (key in collection)
for a long time. But notice that
when you use this form of the for
loop, the looping variable is the key
into the collection, not the value of an entry in the collection. Thus,
inside the loop you would typically write collection[key]
to refer to the
value.
E4X adds an additional syntax to the language (which can actually be used on
any collection, not just E4X expressions): for each (value in collection)
. I
suspect they did this because using a for
loop is rather painful and
inefficient – who wants to write code like this?
for (var key:* in myService.lastResult.person)
{
// Since we used 'for' instead of 'for each', we now
// have to write this ridiculous and inefficient code:
var value:* = myService.lastResult.person[key];
...
}
So when writing E4X code, you almost always use for each
:
for each (var value:* in myService.lastResult.person)
{
...
}
The only thing I wish was different was, it took me a long time to memorize
that “for” gets keys and “for each” gets values. In general I am pretty good
at keeping track of lots of little details, but with so many different
variations on foreach keywords in all the languages I use from time to time –
ECMAScript, Java, C#, PHP, Bourne shell, C shell, etc. – I always find it hard
to remember the foreach syntax for any given language, and the added complexity
of having two collection-looping syntaxes in ActionScript is almost more than
my little brain can bear. I wish there was for keys (var k:* in collection)
and for values (var v:* in collection)
or something like that.
for each (child in parent.*)
, not for each (child in parent)
Say you have this document:
var mydocument:XML =
<root>
<cat>
<mouse />
<mouse />
<mouse />
</cat>
</root>
var cats:XMLList = mydocument.cat; // returns list with one entry
You now want to iterate over all the mice that are inside those cats.
Well, for getting the properties of a regular ActionScript object, you write code like this:
var o:Object = ...;
for each (var property:* in o) ...
So you might think that you do the same thing here:
for each (var mouse:XML in cats) ... // wrong
Alas, E4X is different. In E4X, “cats” is actually a collection – a list of
all the
To fix this, you instead use an E4X expression that specifies exactly which
child nodes of the
// iterate over all immediate children
for each (var mouse:XML in cats.*) { ... }
// or if you prefer: iterate over only those immediate
// children that are <mouse> nodes
for each (var mouse:XML in cats.mouse) { ... }
When you begin to learn E4X, you learn that there are two main data types,
XML
and XMLList
. That seems simple enough. But then after a while, you
start to notice places where it seems like the “wrong” type is being used, or
where impossible things are happening.
var mydocument =
<root>
<rabbit name="Brownster Johansson McGee" />
</root>;
// 'mydocument.rabbit' means to get a list of ALL <rabbit>
// nodes, so myPetRabbit must be an XMLList, right? But
// I'll call my variable 'myPetRabbit', not 'myPetRabbits',
// because I happen to know that I have only one pet
// rabbit.
var myPetRabbit:XMLList = mydocument.rabbit;
// What's her name? Hey wait a minute, "her" name?
// Why does this next line work? Isn't myPetRabbit an XMLList?
// What does it mean to get an attribute of a list??
trace(myPetRabbit.@name);
The reason this works is that E4X intentionally blurs the distinction between
XML
and XMLList
. Any XMLList
that contains exactly one element can be
treated as if it were an XML
. (Furthermore, in this example, even if
‘myPetRabbit’ held a list of more than one node, myPetRabbit.@name
is still a
legal expression; it simply returns a list of all “name” attribute nodes of all
of those
In fact, if you search the E4X spec (PDF) for “blur”, you will find 15 usages of the phrase “… intentionally blurs the distinction between….”
For example, another place where this blurring is evident is in the behavior of
XMLList.toString()
. As the Flex docs say:
So if an XMLList contains <node>hello</node>
, then toString()
will return
"hello"
; but if the list contains <node>hello</node><node>goodbye</node>
,
then toString()
will return "<node>hello</node><node>goodbye</node>"
(not
"hellogoodbye"
). Presumably this decision was made in an effort to achieve
“do what I mean” behavior, where the output would match what developers most
often intended; but personally I find it a little confusing. If you really
need the full XML version of an XMLList that contains simple content, use
toXMLString()
instead of toString()
.
When you are working with ordinary Objects, there is nothing wrong with writing code like this:
var x:Object = ...;
if (x.y.z == 3)
foo(x.y.z);
That code will usually run very fast. The only possible problem is if “y” or “z” is actually a getter with a slow implementation, but that doesn’t happen too often.
But with E4X, it is much more likely that repeating an expression such as “x.y.z” will mean that an expensive lookup operation has to be done a second time:
var mydocument:XML = ...;
if (mydocument.cat.mouse.length() == 3)
foo(mydocument.cat.mouse);
This looks very similar to the plain-Object example, but the difference is that “mydocument.cat.mouse” is actually executing a whole lot of code behind the scenes. It has to walk through your XML structures, figure out which XML nodes match the given expression, and then create a new XMLList containing only those nodes.
So if you need to re-use the result of an E4X expression, you will usually want to save it in a temporary variable:
var mydocument:XML = ...;
var mice:XMLList = mydocument.cat.mouse;
if (mice.length() == 3)
foo(mice);
If you debug your app, the Variables will can help you see what values have been assigned to your XML and XMLList variables. The main pane of the Variables view shows a hierarchical view of the XML variable, and the detail pane shows the full XML text: