Tuesday, July 24, 2007

ActionScript 3, E4X Snippet

I have been working on an E4X function to transform XML attributes into elements for displaying in a Tree.  I have a working function, not exactly in the form I wanted, but I am lacking a good example of it to post this week.  This is all tied into the work I have posted in the last few weeks about sorting XML for trees.

Code snippet

There was one little code snippet which was part of solving the problem I thought I would share with you now.  Unlike my XML sort routines where I modify the source object, this routine creates a duplicate XML object but with elements instead of attributes.

The problem I had was coming up with code to copy the root element of the source code to a new XML object.  It wasn't so straight forward with E4X.  I think concise code could have been written to simply copy the object with the copy() function and the delete its children but I was concerned this could be inefficient especially if the object was large.

This is what I did:

 1 public static function transformXmlAttributeToElement
 2     ( avXmlIn               :XML,
 3       avAttributeName       :String,
 4       avArrayAttributeNames :Array )
 5     :XML
 6 {
 7     var lvXmlOut:XML = <{avXmlIn.name()}/>;
 8     for each( var lvXmlAttribute:XML
 9                in avXmlIn.attributes() )
10        lvXmlOut.@[lvXmlAttribute.name()] = lvXmlAttribute;
    ...

Line 7 creates a root element with the same name as the source.  Lines 8 and 9 loop through all of the attributes in the source root element with line 10 creating a duplicate of each attribute in the destination element.

My generic functions can't use hard coded names, but most examples you find on the Internet use hard coded names.  I thought maybe someone needing to do something similar could benefit from the example.  I prefer to write more comprehensive posts than this but I'm just not ready this week.

Addendum

My post More Hierarchical Sorting E4X XML: for Flex Tree & Beyond was my 1st attempt to make use of publishing a Flex application along with the Publish Application Source feature of Flex Builder 2.  Boy am I dumb.  For some reason it got into my head that every time I recompile, the new source code would be regenerated.  Not true.  If you downloaded the source code before last Friday, you got old source code.  I've fixed that.

I also fixed a state transition flicker problem that crept back into the example.  There appears to be a little bit of art in handling flicker in state changes.

Wednesday, July 18, 2007

ActionScript 3, Undocumented Features?

You have been entrusted to be in on some secret features of ActionScript 3.  Only a handful of programmers around the world will see this information.  This is mostly due to the fact you have been able to hack your way into one of the most obscure blogs on the planet!

Actually, I don't know if I am breaking new ground here but I haven't been able to find any mention of this feature.  I really discovered it sometime ago while writing an earlier post but I didn't know the extent of the find.

And the find is fairly extensive but I've come up with only a few practical uses and one of those was already discussed in the mentioned post.  I don't know how far back these features go.  It may be they were around before ActionScript 2; before I ever used Flash.  Maybe veteran Flash developers find it so ho-hum they forget to pass it on to newbies.  I haven't tried this with AS2 because I don't want to fire up Flash again and have to re-learn anything.  BE WARNED: Be careful how you use undocumented features.  Also, "undocumented" means I haven't been able to find anything about them in the Adobe documentation.

Please let me know if you can find this someplace.  Someone needs to write a book about how to best use and find available resources for Flash, Flex and ActionScript.

The Secret Features:

You can insert multiple comma separated expressions when the syntax calls for an expression between parentheses.

For example:

do
{
    // do something
} while( a = b, a == 0 );

The while test will be performed on the rightmost expression, a == 0, but variable a will be set to the value of b prior to the test.  You are not limited in the number of expressions but you are limited in the type of expression.  For example, you cannot declare a variable with a var and you can't use an if statement.  The expression must resolve to a value.  The ? : expression can be used and you can call a function.

The do while statement, the while statement and the E4X filter predicate expression are the best candidates for this feature.  However it can also be done in the return and switch statements although I can't see any value in using it.  The return statement doesn't even require parentheses.

I was hoping to show a compelling example using do while or while but haven't been able to think of one.  I made this discovery when I was creating a loop and using IViewCursor to iterate over an XMLListCollection.  The where clause was getting too long to fit into a line and I wanted to break it up.  So I thought, what the heck, why not try it.

You can also do something similar with the for statement:

for( I = 0, N = 2; N < 5,I < 3; I++, N++ ){}

I think I recall seeing examples of the for statement before, but I don't remember if it was for ActionScript.

I guess when the ActionScript parser encounters the open parenthesis it handles it as though it is a function call with arguments without paying too much attention to the context.  It's okay, I won't tell Adobe if you won't.

Monday, July 9, 2007

More Hierarchical Sorting E4X XML: for Flex Tree & Beyond

This is a continuation of my most recent posts.  I've changed the Tree's dataProvider to an XMLListCollection object instead of an XML object as discussed in a recent post in this series.

My hierarchical sort function still works with an XML object so it may be used for other purposes besides a Flex Tree.  The sort function is only dependent on the resources built in the Flash Player.

The earlier sort function had a limitation which required sorting an attribute that needed to be in all elements of the XML object.  The new sort function no longer has that requirement.  This opens the door for even more functionality.

The new function still only sorts on a single attribute but if you combine that with some old tech you can do quite a bit.

Old TechDo you know what this is?  No, it's not a computer but it frequently worked as a stunt double for computers in 60's and 70's movies.  It is a card sorter and I have used one many times. 

I still vividly recall a conversation I was having with my programmer son a few years ago.  We were driving up Highway 6 and it's one of those events in your life when you remember where you were.  We were having a recurring argument about cutting 25 years from my resume.  My son asked me what could you possibly use today that you learned in those days.  I guess I remember this incident so well because it forced up the reality that so much of my professional self esteem is based on things nobody cares about today.  It was a good question albeit a little cold.

Well here it is: I can use my experience of operating a card sorter today!  And do you know how to sort on one, son?  Of course you don't!  A field in a card must be sorted one column at a time backwards.  Multiple fields must be sorted in reverse order.  For example if you wanted to sort by last name in columns 1 thru 10 and first name in columns 11 thru 20 you would sort on columns 20,19,18...3,2,1; one at a time.

And if you combine this old tech with my simple function you can sort multiple attribute fields each one independently of the others.  That means you can sort some ascending, some descending, some dates, some numbers, etc. You can do it backwards (with one hand tied behind your back).  Hah!

No, you don't have to sort a character at a time.

Here is the new ActionScript 3 E4X sort XML attributes function:

 1 public static function 

sortXmlAttribute
 2     ( avXml                :XML,
 3       avAttributeName      :String,
 4       avPutEmptiesAtBottom :Boolean,
 5       avArraySortArgument0 :* = 0,
 6       avArraySortArgument1 :* = 0 )
 7     :void
 8 {
 9     var lvChildrenCount:int
10         = avXml.children().length();
11     
12     if( lvChildrenCount == 0 )
13         return;
14 
15     if( lvChildrenCount > 1 )
16     {
17         var lvAttributeValue    :String;
18         var lvXml               :XML;
19 
20         var lvSortOptions:int
21             = avArraySortArgument0 is Function
22               ? avArraySortArgument1
23               : avArraySortArgument0;
24             
25         var lvSortCaseInsensitive:Boolean
26             = ( lvSortOptions & Array.CASEINSENSITIVE )
27               == Array.CASEINSENSITIVE;
28               
29         var lvArray:Array = new Array();
30         
31         for each( lvXml in avXml.children() )
32         {
33           lvAttributeValue
34               = lvXml.attribute( avAttributeName );
35 
36           if( lvSortCaseInsensitive )
37               lvAttributeValue
38                   = lvAttributeValue.toUpperCase();
39                 
40           if( lvArray.indexOf( lvAttributeValue ) == -1 )
41               lvArray.push( lvAttributeValue );
42                 
43         } // for each
44         
45         if( lvArray.length > 1 )
46         {
47             lvArray.sort
48             (
49                 avArraySortArgument0,
50                 avArraySortArgument1
51             );
52             
53             if( avPutEmptiesAtBottom )
54             {
55                 if( lvArray[0] == "" )
56                     lvArray.push( lvArray.shift() );
57             } // if
58             
59         } // if
60             
61         var lvXmlList:XMLList = new XMLList();
62         for each( lvAttributeValue in lvArray )
63         {
64             for each( lvXml in avXml.children() )
65             {
66               var lvXmlAttributeValue:String
67                    = lvXml.attribute
68         ( avAttributeName );
69                      
70               if( lvSortCaseInsensitive )
71                   lvXmlAttributeValue
72                     = lvXmlAttributeValue.toUpperCase();
73                 
74               if( lvXmlAttributeValue
75                   ==
76                   lvAttributeValue )
77                   lvXmlList += lvXml;
78                     
79             } // for each
80             
81         } // for each
82         
83         avXml.setChildren( lvXmlList );
84         
85     } // if             
86 
87     for each( var lvXmlChild:XML in avXml.children() )
88     {
89         sortXmlAttribute
90         (
91             lvXmlChild,
92             avAttributeName,
93             avPutEmptiesAtBottom,
94             avArraySortArgument0,
95             avArraySortArgument1
96         );
97     } // for each
98     
99 } // sortXmlAttribute

Function sortXmlAttribute Description

Arguments

# Name Type Description
1 avXml XML Object to be sorted.
2 avAttributeName String Attribute Name to sort
3 avPutEmptiesAtBottom Boolean Where to put elements that have empty values for sorted attribute.
4 avArraySortArgument0 * Array.sort() 1st argument
5 avArraySortArgument1 * Array.sort() 2nd argument

 

There were two big changes from the earlier function.  One, I have removed E4X filtering and used the more conventional E4X syntax.  This really simplified the function.  I'm not putting down the filtering feature, but in this instance I am better off without it.

The second was dealing with case insensitive sorts.  The problem I had before was that I ended up separating strings that had different cases but were otherwise equal.  This isn't a problem when you are doing a single sort, but when you stack sorts back to back the problem shows up.

I also added a feature that controls where you put the XML elements that do not have an attribute value.  The need do this varies with the type of data you are looking at.  It was actually easy to implement.

Lines 20 thru 27

This code determines if the sort is case insensitive.

Lines 29 thru 43

This section builds an array of unique attribute values.  If the sort is case insensitive I convert all of the values to upper case.

Lines 47 thru 51

This sorts the array with the Array.sort() function.  That's all I ever sort; just the unique attribute values.  This is important.  I don't sort the XML object (because I can't).  I never upset the order of the children which may have been sorted on another field previously.

Lines 53 thru 57

This is where I move the XML elements that have empty attribute values if necessary.  It just moves an empty string from the beginning of the array to the end.

Lines 61 thru 81

This is where the real meat of the function is.  I want to build a new XMLList object that represents all of the XML children but in the new sorted order.  I iterate through each of the sorted unique attribute values.  Inside that iteration I iterate through all of the XML children.  If the child has the current attribute value I append it to the end of the temporary XMLList I'm building.  I have to allow for case insensitivity.  It is important make sure that children with the same value stay in the same order.  This actually means doing nothing and I do it well.

Line 83

I replace the children of the input XML object with the temporary XMLList I just built with the E4X setChildren function.  It is really nice to have this very simple function.

Lines 87 thru 97

This makes the function recursive.

That is the function.

It is very straightforward.  If all algorithms were like this, most everyone could be a programmer.

The Example

Previously, I have included the source code in the post but no compiled example.  I know personally I prefer to just look at the code and if I'm interested I'll compile it myself.  I've also kept the examples to one file.  I started this post and the example in the same manner, but the example just kept getting bigger.  It is still only one file, but in the future I will have to break it into more than one class.  I've decided to provide a page with the source code.  My intention is to do this in the future, but the page will probably contain more than one file.  In addition I have a link to a page that contains the SWF file.  I will also compile with the "Publish Source Code" option.  This will give you the ability to view the source code from the SWF program.  You'll also be able to download the program in a zip file along with the Flex 2 SDK.

 

The new example has a lot more goodies in it.  There are 2 built-in examples but you can also paste in your own XML.  So it actually can be used as an XML sort tool.

Next

I'm now starting to think about sorting element names and values.

Addendum

I came across someone wanting to sort XML via the E4X capabilities of JavaScript.  I think my function will work.  You might have to remove the static declaration. (I don't know.)

There are several functions in the example declared as static.  This is my way of implying the function stands on its own and it may have other uses.

I wouldn't mind expanding to other platforms if for no other reason than to improve my visibility.  (I probably only have a total of 30 lifetime hits.)  Please point me in the right direction on how to set up a test environment of E4X JavaScript.

Monday, July 2, 2007

ActionScript 3 isNaN is BaD

A primary rule of my programming style is to keep things positive whenever possible.  For example it always clearer to test for things to be equal than to test for them to be not equal.  So I'm not surprised there are many discussions about the ActionScript isNaN function on the Internet.  NaN stands for "not a number".  Its roots go deeper than ActionScript.  It is self-evident why the term was coined and equally predictable to be a subject of confusion.

However, the function's negativity can only be charged with creating "bad vibes" in the misdeeds mentioned in this post.  The following comes from the Adobe documentation: 

 

The last two table rows in the documentation pass strings to the isNaN function.  However, I get a fatal compile error when I try to do this.  The same documentation clearly specifies the argument type is "Number".  The editor's "Intellisense?" (I don't know what Adobe calls it) says the argument is a "Number".

But I can find plenty of examples on the Internet showing string arguments.  I think some of these examples may be explained by the fact that they don't specify the type in the var declaration.  When I do this, I get a compiler warning.  I wouldn't post an example that produced a compiler warning, at least without pointing it out, but maybe I'm in the minority.

Other examples may be about earlier versions of ActionScript or maybe beta versions.  It's sometimes hard sort these things out.  I am using Flex Builder 2 and have also tried this with the Flex Builder 3 beta.  I got around the warning message by using the asterisk as the variable type.  I don't like using an asterisk as a workaround, but in this case I think that is how the argument is internally defined.  This is because the function actually works when I pass a string to the function.

I really only wanted to write a function to determined if a string contained a valid number.  The following example works.  Notice the asterisk in the "for each" statement.

<?xml version="1.0" 

encoding="utf-8"?>
<mx:Application
    xmlns:mx="http://www.adobe.com/2006/mxml"
    layout="absolute"
    creationComplete="onCreationComplete()"
    >
    <mx:TextArea
        id="Text1"
        height="400"
        />
    
    <mx:Script>
        <![CDATA[
        // -----------------------------------------
        // onCreationComplete
        // -----------------------------------------

        private function onCreationComplete
            ()
            :void
        {
            var lvString:String = "";
            
            for each( var lvValue:*
                  in ["5", "A", "-1", "-.7", "*" ] )
                lvString += lvValue
                          + ": NaN is "
                          + isNaN( lvValue ).toString()
                          + "\n";
            
            Text1.text += lvString;

        } // onCreationComplete
        ]]>
    </mx:Script>
    
</mx:Application>

Text1 contains the following when the example is run:

5: NaN is false
A: NaN is true
-1: NaN is false
-.7: NaN is false
*: NaN is true

This has been a minor distraction while developing an example program for a new post on my series of Sorting XML which I hope to have ready this week.