Ways to use E4X to filter data in ActionScript 3

ActionScript 3 in Flash 9 includes powerful support for reading and manipulating XML. It’s called E4X, and it gives us developers some useful new syntax. Read on for a super quick introduction to E4X followed by some powerful ways to filter your data using this feature.

E4X is powered by the new XML and XMLList classes. The most common way to declare a variable of one of these types is to pass it and XML string. However, many developers might miss the fact that you can declare XML directly within your classes or ActionScript code. It’s supported natively by the compiler.

var data:XML =
	<items>
		<item name="Wii">
			<source>Amazon</source>
			<price>364.00</price>
		</item>
		<item name="Wii">
			<source>Target</source>
			<price>249.99</price>
		</item>
		<item name="X-Box 360">
			<source>Amazon</source>
			<price>399.99</price>
		</item>
		<item name="PlayStation 3">
			<source>Amazon</source>
			<price>599.99</price>
		</item>
	</items>;

E4X lets you drill down into the data to get the information you need without any excessive looping or strange calls to parent and child nodes. The following example gets a list of all the item names defined in the XML from the previous example. Notice that the name attribute is specified with the “@” symbol at the beginning.

var itemNames:XMLList = data.item.@name;

To take it a step further, you can filter the same set of data to find items with “Amazon” defined as the source value. Place a statement within the parentheses to check for a specific value. In this case, we check to see if an item’s source is equal to a string value.

var amazonItems:XMLList = data.item.(source == "Amazon");

Interestingly enough, you can place just about any ActionScript statement within the parentheses. In the following example, I’ve included a trace statement to display each item’s name in the console. I find this particularly useful for debugging.

data.item.(trace(@name));

You can filter by multiple fields as well. This example filters items from Amazon with a price under $400.00.

var items:XMLList = data.item.(source == "Amazon" && price 

Let's take it a step further. Say that your application filters items by source, like in the example where we only wanted items from Amazon. However, the filtering is controlled by the user through a ComboBox or another user interface component. E4X will let you compare attributes or children against a variable as well!

var sourceName:String = "Target";
var itemNames:XMLList = data.item.(source == sourceName);

Please note that you must be sure the variable name is different than the name of the child. If I had named my variable source, instead of sourceName, the statement within the parentheses would never access the item's source value because it would always use the variable. In exact terms, (source == source) most definitely won't work!

Let's make things a little more interesting. What happens if the name of the value by which we're filtering should be dynamic as well? In the previous examples, we've accessed attributes directly, but you can call functions on the XML object too. Here, we call the attribute() to get an attribute's value by its name. This will also work for the child function.

var filterBy:String = "name";
var filterValue:String = "Wii";
var items:XMLList = data.item.(attribute(filterBy) == filterValue);

Finally, let's finish with something a little complex. Say the application's interface allows the user to filter across multiple fields, but each field is optional. We can do that too, but not directly through E4X (as far as I know; please prove me wrong!). This example shows how to filter by items from Amazon that cost exactly $599.99, but the fields Array could contain any number of items with additional items to filter by.

var filtered:XMLList = data.item;
var fields:Array = [{name: "source", value: "Amazon"}, {name: "price", value: "599.99"}];

var fieldCount:int = fields.length;
for(var i:int = 0; i 

I'm sure you can think of interesting ways to expand on that last example to create some very powerful filtering systems. In this particular case, I've only checked for equality, but with some simple changes to the logic, you could check for prices greater than or less than a certain value, or you could even search for substrings within a value. For instance, you might want to search for "PlayStation", and include results for PS2 and PS3 systems. E4X is very, very powerful.

Update: I've written two followup articles:

About Josh Tynjala

Josh Tynjala is a frontend developer, open source contributor, bowler hat enthusiast, and karaoke addict. You might be familiar with his project, Feathers UI, an open source user interface library for Starling Framework that is included in the Adobe Gaming SDK.

Discussion

  1. Pingback: Xrum! Blog… » Blog Archive » Google and Flex

  2. Pingback: More XML filtering with E4X in ActionScript 3 » Zeus Labs

  3. Kris

    Thanks for posting this, using variables as node name & value filters was exactly what I was just trying to do. You explained it very nicely, thanks again.

    (Better to comment late than never!)

  4. Steve

    Excellent piece, a huge help. A question: are there any filters that allow filtering by START of a string, as in all books whose authorName starts with “C”?

    Thanks.

  5. Pingback: reintroducing.com Blogging Receptacle » Blog Archive » E4X Articles

  6. Devon O.

    I realize this excellent blog entry’s a bit old, but I just ran across it and thought I’d point out you can filter on multiple fields directly with e4x along these lines (like if you wanted a Wii under 300 bucks, for example):

    var sortItem:String = "Wii";
    var maxPrice:Number = 300;
    var filteredList:XMLList = data.item.(@name == sortItem && price 
  7. Josh Tynjala

    Devon, I filtered two fields just like that in the example above where I wanted items from Amazon for less than $400. I assume you’re responding to my last example, where I use a loop to handle multiple filters. That example is for the case where you don’t know how many different fields you’re filtering by until runtime. The user might choose the specific filters needed similar to how smart playlists work in iTunes. There could be two, three, or any number of filters. In that case, it’s impossible to use straight E4X because you don’t know the exact filters needed until the user picks them.

  8. Duane Hardy

    In flex, I am using remoteobject call to a coldfusion cfc to return complex xml from a yahoo API. To use the above described method in actionscript do I have to declare “E4X” as my return type anywhere (like the httpservice) or can I just continue to return the xml from the cfc?

  9. Josh Tynjala

    Duane,

    I can’t give you a definitive answer on that one, as I don’t work with RemoteObjects or CF. If your current returned data is an instance of the XML class (which is what is used by E4X), then you should be fine. If it’s a String as far as ActionScript is concerned, then you’re probably going to have to set a result format or do the conversion manually.

  10. Metal Hurlant

    Neat. That’s going to sound funny, but I hadn’t realized one could filter on sometimes missing attributes within E4X:

    xmlList.(@attr == "2") will throw an exception if any of the children of xmlList doesn’t have an attribute “attr” present.
    However, xmlList.(attribute("attr")==2") works just fine.
    I wish I had thought of that 6 months ago.

    Also, your last example can be rewritten as

    var filtered:XMLList = data.item;
    var fields:Array = [{name: "source", value: "Amazon"}, {name: "price", value: "599.99"}];
    filtered = filtered.(fields.every(function(item:*,index:int,array:Array):Boolean {
        return child(item.name) == item.value;
    }));

    Technically, it’s one E4X expression, although it doesn’t exactly improve the code maintainability.

  11. Nick

    How can i filter an XML document to give me only one value if there are values that are the same?

    Basicly.
    I am pulling a XML document with a and then enabling the dates in my datechooser by creating disabledranges. My script can only handle one date value of 12/12/2007 but i have 12 dates that equal 12/12/2007.

  12. Ric Moore

    Your last example, filtering when you don’t know if an attribute exists – here’s the E4X solution:

    var filtered:XMLList = data..item.(
        hasOwnProperty("@source") &&
        @source == "Amazon" &&
        hasOwnProperty("@price") &&
        @price == "599.99");

    From Essential Actionscript 3.0:

    To filter a list where not every item has a given attribute or child element, we must use hasOwnProperty( ) to check for the existence of that attribute or child before filtering on it. Otherwise, a reference error occurs. For example, the following code returns every element in someDocument that has a color attribute set to “red”:

    someDocument..*.(hasOwnProperty("@color") && @color == "red")
  13. Josh Tynjala

    Actually Ric, in the last example, it’s meant to show how a user might choose to filter some fields, but not others. In that case, I assume that all fields must exist in the XML.

    To improve your suggestion, the E4X solution for a field that may not exist would be to use the attribute() function. It will not throw an error if the attribute is missing. It just returns null.

    attribute("source") == "Amazon" && attribute("price") == "599.99"
  14. Mathias Wedeken

    Josh,

    cool summary. E4X is one great beast, that´s true.something that is also worth noting is that you don´t have to format the xml when you generate it within your class, it will be done automatically, so there´s no need anymore for “ignore whitespace2 or what it was called again. that´s also very nice when outputting the xml to a server side PHP script to save it to disk, the xml file you´ll get is very well structured and perfectly readable, not like the old times where you had just a huge pile of code…

    anyways, just something.

    best

    mathias

  15. Yasir

    It is very helping example. I have some problem to filter data in XMLList. I want to show the elements of the XMLList in tree control. But I don’t want to show the leave nodes.
    May any one help in that problem?

    Thanks

  16. Josh Tynjala

    Yasir, if you don’t need the leaf nodes at all, you can delete them. Otherwise, you should learn to create a custom implementation of mx.controls.treeClasses.ITreeDataDescriptor.

  17. cdm

    I found a problem in using the same syntax:
    var items:XMLList = data.item.(source == “Amazon” && price <400);

    into a binding expression like

    dataProvider=”{data.item.(source == “Amazon” && price <400)}

    it will not accept the &&

    Any idea – apart from doing it in AS?

  18. Josh Tynjala

    cdm, I’m guessing the problem is that you need to use &amp; instead of the straight ampersand. MXML is a dialect of XML, so it requires encoding for special entities (exception: CDATA blocks, but that’s not possible in an attribute).

  19. Lefty

    Hi,
    thanks for a great and utterly useful post!
    I do have a problem though; any idea if it’s possible to add elements together and compare them to a variable within the parantheses?

    Like this for example:
    XMLList.(available == “Yes” && (from_price + from_tax + from_postage) >= minCost && (to_price + to_tax + to_postage) <= maxCost).@ID;

  20. VegasPat

    Hi Josh,

    In your example, how would you filter an item that contains the number 3 in it’s name so that your results would return Playstation 3 and X-box 360? Thanks for your posts and your help.

  21. Nick

    You deal with clean XML feed here, but i like em dirty.

    I keep getting the error –
    Error #1009: Cannot access a property or method of a null object reference.
    -because not all my nodes have the attribute ‘contactname’ so i need to check if that attribute is apparent before trying to add it to my array.

  22. Josh Tynjala

    Nick, I’m guessing you included some XML in your comment, but the blog software stripped it out. Use &gt; and &lt; in the future to replace > and <.

    If an XML element may not have an attribute like you describe, then @contactname won’t work because you’ll get a runtime error. Instead, use attribute("contactname"). This XML method is available for the exact use-case you described.

  23. Pingback: evolutioneer.com/blog » Blog Archive » Manipulate XML Data in AS3

  24. mr.moses

    how would you filter if there was more than one source element in each item element?

    instead of having two Wii items, if there is one Wii item with multiple source elements (one for Amazon and one for Target), the Wii item will is not selected if you filter on Target.

    <item name=”Wii”>
    <source>Amazon<source>
    <source>Target<source>
    </item>

  25. Josh Tynjala

    mr.moses, with two source elements, the following seems to work for me:

    data.item.(source.contains("Target"))

    I didn’t know about the contains() method until today, so thanks for the great question.

  26. Matt Long

    Is there a way to filter and display by a specific date…
    For example if I have a list of events but I only wanted to display the 4 most recent, is there a way to grab a date element from the XML file and compare it with the Date/Time on a users machine?

  27. Josh Tynjala

    Matt Long, it should be possible. How easy it will be depends on how the date is formatted in the XML, though. The Date constructor with one argument or Date.parse() could be useful for extracting date values for comparison.

  28. Daniel

    Is there any way to only get the parent nodes with toXMLString() and not the child nodes?

    I have the following:

    <root>
    <parent></parent>
    <parent><child></child><child></child></parent>
    <parent></parent>
    </root>

    All I want is to filter out the above XML and only get the parent nodes printed out without their children.

  29. shen

    I have following XMLLIst I want to get books and Songs as tabnavigator tabs(dataprovider),Is there way to get XMLLIst to dataprovider or array collection.because under the dynamic properties always change books,songs sometimes it may books,films,songs.
    so I want to appear tabnavigator tabs books,films,songs..

    1

    2

    Thank you in advance,Hope flex star may help me!!

  30. Pingback: andy.edmonds.be › links for 2009-07-12

  31. Pingback: Parenthesis – the round mystery « Max Blog

  32. Giles

    Josh, absolutely great post and has helped me out a lot but I seem to have discovered an intractable problem. I am attempting to filter for two attributes within descendants.

    I’ve tried using every method I could think of – (contains); attribute(“”); hasOwnProperty(“@”); att==; but none will work. Flash seems to ignore anything that comes after the &&.

    Here is one of the lines I’ve tried, can you think why it won’t work?

    mediaFilterDays = filterDays.(descendants(“programme”).(@genre == Filter && @media == mediaFilter));

  33. jon

    trying to figure out how to sort if an item has more than one of the same node type…

    such as:

    <item name="Consoles">
    <source> PS3 </source>
    <source> PS2 </source>
    </item>

    if I searched for all items with PS2 this would be skipped with the methods used above…

  34. Josh Tynjala

    Jon, if you have multiple elements as children, it might not be possible to do it in a single statement. You might have to put source into an XMLList and search that separately.

    However, you might also look at contains() on XMLList. It might be useful here. I’ve never played with that one, though.

  35. Kristin

    Hey, great article, but I’ve run into a snag with your last example. I cut and pasted your code and get this in my output window:

    (your code and your xml file which I swear I did not change)

    var filtered:XMLList = data.item;
    var fields:Array = [{name: "source", value: "Amazon"}, {name: "price", value: "599.99"}];
           
    var fieldCount:int = fields.length;
    for(var i:int = 0; i <fieldCount; i++)
    {
        var fieldName:String = fields[i].name;
        var fieldValue:String = fields[i].value;
        filtered = filtered.(child(fieldName) == fieldValue);
    	trace(filtered);
    }

    this code is supposed to return Amazon items worth 599.99, however it returns 3 Amazon items. It's returning 2-599.99 items and 1-399.99 item which is not what's in the xml file.

    output results

    Amazon
    399.99

    Amazon
    599.99

    Amazon
    599.99

    I’m new to xml and AS3 so I wanted to make sure this wasn’t happening because I’m using Flash CS4 (since your original post is almost 3 years old)

  36. Josh Tynjala

    Put the trace() call after the loop and you might see the correct result. During the loop, it’s still filtering by field name and value, and that means it will search over each of the items more than once.

  37. Kristin

    Thanks Josh, that was it and I should have tried it. I only had intro classes to Flash MX2004 and that’s been almost 3 years ago. Kinda rusty.

  38. Kristin

    By the way, while I’m here… I’m trying to write a picture viewer program and I’ve seen a lot of tuts on the web, but they don’t seem to address my needs. I have 3 xml elements: name, file (an image), and comments. I have so many image files that I’ve chosen to load them into a list component which I can load up with name data. For some reason I’ve hit a road block in that I don’t know how to link the selectedItem in the list box so I can display the other elements on the page. I hope this isn’t too complicated a question to ask. Thanks

  39. Auzzie

    Great post, I was able to use a couple of scripts above but ran into an issue and was wondering if anyone had some insight:

    I have some data that I separated into labels of a combobox:


    Features3
    Features1,Features2,Features3
    Features2,Features3
    Features1
    —-
    if (featuresCombo.selectedLabel != item.FEATURES)
    return false;

    return true;
    —-

    If you select ‘Features3’ for example it doesn’t return the selections that have multiple entries separated by commas.

    Is there a way to filter based on the occurance of a string rather than matching the whole string?

    I tried to use the (contains) function from above and was getting this error:Error #1123: Filter operator not supported on type mx.utils.ObjectProxy.

    Thanks for any help.
    -Auzzie

  40. Pingback: AS3/E4X: Rekursiv alle Unterknoten mit einem Attribut finden / crusy.net

  41. Bertv

    Excelent post!

    I modified the addUniqueValue function a little so it will also deal with multiple child elements.

    private function addUniqueValue(value:Object, list:XMLList):XMLList{
    	for (var i:int=0;i<value.length();i++){
    		if(!list.contains(value[i]))
    		{
    			list += value[i];
    		}
    	}
    	return list;
    }
  42. Shakey

    Great article, really useful for me to get up-to-speed on the stuff I’ve been missing out on for so long.

    One thing that would be really useful for me would be an E4X equivalent of SQL’s IN command, in that I want to create a filter based on a collection I’ve generated previously.

    Example:
    I have a bunch of “zone” nodes that contain “userid” children, which hold a numeric value. I also have a bunch of “person” nodes with an “id” attribute.

    For a given “zone” node, I get an XMLList of the values of all the “userid” child nodes. I want to use this this to return the “person” nodes whos “id” attribute matches the IDs stored in the list.

    In simpler terms, the E4X equivalent of

    SELECT person
    FROM people
    WHERE id IN (x,y,z,…)

    I have no problem at all just looping through the collection and doing an E4X filter for each, I was just wondering if there’s a more elegant way of doing it.

    Again, great articles – thanks!

  43. LEx

    Need a book with more information on this, who is happy with only a few random bits of code like this ?

    anyone know a good book on A4X ????