Mike Morearty
13 Mar 2007

Common E4X pitfalls

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.

1. Set resultFormat="e4x" on your HTTPService

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

2. E4X expressions don’t reference the topmost tag of the XML

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"?>

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 elements?

It is a very common mistake to think that your E4X expression needs to specify the top-level tag:

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 nodes is:

var personNodes:XMLList = myService.lastResult.person; // right

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

4. Use for each (child in parent.*), not for each (child in parent)

Say you have this document:

var mydocument:XML =
      <mouse />
      <mouse />
      <mouse />
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 nodes (remember, “cats” is an XMLList, with just one entry in this example), and when you iterate over “cats”, that means you want to get one each time through the loop.

To fix this, you instead use an E4X expression that specifies exactly which child nodes of the you want:

// 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) { ... }

5. E4X intentionally blurs the distinction between XML and XMLList

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 =
    <rabbit name="Brownster Johansson McGee" />

// '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??

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

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

6. Warning, a simple-looking expression can be expensive

When you are working with ordinary Objects, there is nothing wrong with writing code like this:

var x:Object = ...;
if (x.y.z == 3)

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)

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)

The debugger can help

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:

XML variables

comments powered by Disqus