I see this question a lot: "How can I select nodes that contain a certain attribute using E4X? I keep getting a reference error saying the variable doesn't exist..." The answer is to use the hasOwnProperty method in predicate filtering.
The hasOwnProperty method checks to see if a string property name is present, and returns a boolean value. By using "@" as the first character in the property name, hasOwnProperty will check to see if an XML object has a particular attribute.
Using hasOwnProperty instead of just looking for "@hex == " something or other will avoid the error ReferenceError: Error #1065: Variable @hex is not defined on the nodes that don't have the hex attribute. This is because the predicate filtering loops over all of the nodes in the previous XMLList to try to match the expression, and if @hex doesn't exist on the current node being inspected, the error is generated. Switching to hasOwnProperty will gracefully handle the "not found" cases.
The code below shows hasOwnProperty in action, using predicate filtering to select specific nodes.
// Create an XML object that contains two types of color nodes: // One type with a hex attribute, and another with red, green // and blue attributes. var colors:XML = <colors> <color hex="0xFF0000" /> <color hex="0x00FF00" /> <color hex="0x0000FF" /> <color red="255" green="0" blue="0" /> <color red="0" green="255" blue="0" /> <color red="0" green="0" blue="255" /> </colors>; // Use the hasOwnProperty method in predicate filtering // to find the nodes that have a certain attribute - hasOwnProperty // will return true if the hex attribute (denoted via @) is found // on the node. var colorsWithHexAttribute:XMLList = colors.color.( hasOwnProperty( "@hex" ) ); // Outputs: // <color hex="0xFF0000"/> // <color hex="0x00FF00"/> // <color hex="0x0000FF"/> trace( colorsWithHexAttribute ); // By looking for a false result of hasOwnProperty we can successfully // select all of the nodes that do NOT contain the hex attribute var colorsWithoutHexAttribute:XMLList = colors.color.( hasOwnProperty( "@hex" ) == false ); // Outputs: // <color red="255" green="0" blue="0"/> // <color red="0" green="255" blue="0"/> // <color red="0" green="0" blue="255"/> trace( colorsWithoutHexAttribute );
Unfortunately, due to a compiler bug the above code will not compile in strict compilation mode. You'll need to turn off the strict flag if you want to use this code in the current beta of Flex 2.
To turn off strict compilation mode, open the project properties (Select properties from the Project menu on the menu bar). Select ActionScript compiler on the left, and uncheck the "enable compile-time type checking" option.
I much prefer to compile with strict mode on, but to use this approach it's a necessary evil to leave it off. I can say for certain that there's a bug logged already for this. I'm hoping it gets fixed soon!
Tags: E4X, ActionScript, Flex 2, ECMAScript
Hello Darron,
shouldn't using "color.hasOwnProperty" fix that error and satisfy -strict mode?
var colorsWithHexAttribute:XMLList = colors.color.(colors.hasOwnProperty("@hex"));
trace( colorsWithHexAttribute.toString());
var colorsWithoutHexAttribute:XMLList = colors.color.(colors.hasOwnProperty( "@hex" ) == false );
trace( colorsWithoutHexAttribute.toString());
kind regards,
Peter Blazejewicz
Hi Peter,
No, that doesn't work. The reason is because when the predicate filtering examines the nodes, its looking at an XMLList of all of the <color> nodes already. Those color nodes do not have a child "color" node, and the code will throw another ReferenceError for "Variable color is not defined".
Trying to use this.hasOwnProperty("@hex") will compile in strict mode, but it doesn't work eithger. The "this" refers to the current class instance and not the XML object being examined via predicate filtering.
As an aside, it IS possible to achieve the same effect without using predicate filtering. You have to write some more code, but at least you can leave strict mode on:
// Create a temp xml object to store the nodes that match
var temp:XML = <temp/>;
// Loop over all of the color nodes
for each ( var color:XML in colors.color ) {
// Look for the hex attribute
var possibleAttribute:XMLList = color.attribute("hex");
// If the length is 1, then the hex attribute is present
if ( possibleAttribute.length() == 1 ) {
// Append the good color node to the list of matching nodes
temp.appendChild( color );
}
}
// Convert the list of matching color nodes to an XMLList
var colorsWithHexAttribute:XMLList = temp.color;
// Outputs:
// <color hex="0xFF0000"/>
// <color hex="0x00FF00"/>
// <color hex="0x0000FF"/>
trace( colorsWithHexAttribute );
The above will get the color nodes with a hex attribute. To get the nodes without the hex element, test for length() == 0 instead of length() == 1.
Hello Darron,
after reading your other post on E4X another solution:
private var searchCriterium:String = "*";
private function filterFunction(item:Object):Boolean{
if(item.@[searchCriterium] == undefined){
return false;
}
return true;
}
private function initApp():void{
var list:XMLListCollection = new XMLListCollection();
list.filterFunction = filterFunction;
list.source = colors.*;
trace("filtered by @*");
trace("num of elements: "+list.length);
trace(list.toXMLString());
searchCriterium = "hex";
list.refresh();
trace("filtered by @hex");
trace("num of elements: "+list.length);
trace(list.toXMLString());
searchCriterium = "green";
list.refresh();
trace("filtered by @red");
trace("num of elements: "+list.length);
trace(list.toXMLString());
}
also I was using try..catch..finally to trap that 1065 RTE error to provide custom solution based on "for each" as you pointed,
but using filtered collection is much easier for me,
lesson learned :)
thanx,
regards,
Peter