Posts Tagged “as3”

E4X is a tidy little querying language. Of all the new features in AS3, this one is probably the best.

Most people use E4X for retrieving data straight up, which is good if you have your server-side code doing the filter crunching, but if you’re going for chunky data calls, it’s nice to be able to filter on the client-side too. Thankfully, E4X gives us plenty of capability for filtering.

Let’s start by laying out our common data set. I’m using an XML file with automotive data consisting of Makes->Make->Models->Model. You can see the XML file here: AutomotiveData.xml

In my code, I’ve set a variable called xmlData to the value of the data in AutomotiveData.xml. xmlData corresponds to the root node (Makes) in the data.

Basic E4X filters:

Let’s retrieve the list of Honda models:

trace(xmlData.Make.(@name == “Honda”).Models.toXMLString());

“name” is the attribute of the Make node that we’re working with, so you target attributes with the @ symbol in front. Everything inside of the parentheses is interpreted as normal ActionScript, so you can run pretty much any code that you want to inside there, even if it doesn’t make sense. Just remember, in order to actually filter data, you need to run a function that evaluates to a Boolean: true or false.

Multiple E4X filters:

Let’s get all Makes that have models that cost less than $30,000 and have at least 8 models (this will return Honda and Toyota).

trace(xmlData.Make.(@minMSRP < 30000 && @modelCount >= 8).Models.toXMLString());

Now, you’ll notice that I have an attribute called @modelCount that contains a sum of the model count for that Make. If you don’t have that attribute, you could also count quite easily:

trace(xmlData.Make.(@minMSRP < 30000 && Models.Model.length() >= 8).Models.toXMLString());

E4X recognizes Models as the currently scoped Models node, since we’ve already descended to xmlData.Make. It would not work to say xmlData.(Models.Model.length() >= 8) because the scope of “xmlData.” only gets you to Make, not Models.

In the above example, notice that I ran a function, .length() on the XML, and that I in fact ran E4X inside of E4X. You can nest E4X pretty much as far as you would ever need without hitting any limits. If you do hit limts, you should restructure your XML or consider using a filtering function, which I will talk about later.

Nested E4X and Local Variables

Let’s say we want to find all Makes that have a Model that starts with the letter “R”:

var strModelFirstCharacter:String = “R”;
trace(xmlData.Make.(Models.Model.(@name.toString().indexOf(strModelFirstCharacter) == 0).length() > 0).@name.toXMLString());

Notice that we’re running a nested query so that we can keep the Make scope and get the Make.@name attribute. Also, we’ve passed in a variable this time so that we can modify this E4X to run off of any character or phrase.

If a user was selecting the filters, you could also have them pass in a value, like so:

var strAttribute:String = “mpgCity”; //mpgHighway
var intMinValue:uint = 22;
trace(xmlData.Make.Models.Model.(attribute(strAttribute) > intMinValue).@name.toXMLString());

E4X Filter Functions

Something that just using variables above leaves out is the ability to change the greater than operator without a bunch of messy inline code. However, since we can do basically anything inside of the parentheses, you can create a function to handle variance:

function GenericFilter(data:XML, attributeName:String, value:Object, comparison:String = "equal"):Boolean {
    switch(comparison) {
        case "<":
        case "less":
            return data.attribute(attributeName) < value;
        case ">":
        case "greater":
            return data.attribute(attributeName) > value;
        case "=":
        case "equal":
            return data.attribute(attributeName) == value;
    }
    return false;
}

Then, your E4X can be structured as such:

var strAttribute:String = “seating”;
var intValue:Object = 8;
var comparisonType:String = “=”;
trace(xmlData.Make.Models.Model.(GenericFilter(valueOf(), strAttribute, intValue, comparisonType)).@name.toXMLString());

Notice the valueOf() function; this is passing Model nodes into the GenericFilter, which is why that function accepts an XML node as the data parameter. The comparisonType is simply a string that you can pass in. I’m using a switch because I don’t think there’s a way to pass an actual equality or greater than operator as a parameter in ActionScript. You could of course extend GenericFilter() to accept greater than or equal, or do fuzzy logic, whatever you want to do is acceptable.

These E4X filter functions are pretty much the most powerful thing in E4X; you can run anything that you could possibly want to.

More to come…

I’ll update this article with more information as requests come in or I try new things. Keep in mind that E4X attribute filters don’t work in switch statements.

Comments 2 Comments »

Download the Date Utility referenced in this article: DateUtility.as

Despite what you’ve heard, it’s actually really easy to get a Date() with AS3. You just have to know how the right things to say so that you can smooth-talk her a little bit.

Let’s get started:

Get the current date & time:

var dtCurrent:Date = new Date();

This will give you the current date and time, according to the computer that Flash Player is running on. It will use the computer’s timezone and DST settings.

Create an arbitrary date & time:

var dtWhenever:Date = new Date(2008, 4, 7, 16, 5, 30, 250);
This gives you  May 7th, 2008, at 4:05:30.250pm. Take special care to notice that month (4) is zero-based, meaning that January is 0, and December is 11. Hour, the 4th parameter, starts with 0 as midnight, and ends with 23 as 11pm. The constructor is in the format new Date(year, month, day, hours, minutes, seconds, milliseconds).

Adding to and Subtracting from a date:

Fortunately, AS3 makes this very easy. Say that you want to add 4 days to a date. All you have to do is say:
dtMyDate.date += 4;

Remember, .date is the property that you and I think of as the day of the month. So, you’re probably starting to think of some edge cases, such as “what if it’s the last day of the month?”. The great news is that AS3 will automatically bump the month up as well, rather than having September 34th or something silly.

This works with any of the properties of a Date as well. For example:
dtMyDate.fullYear += 1;
or
dtMyDate.minutes += 300

Subtraction works the exact same way, except you of course would say
dtMyDate.minutes -= 300;

Number of days in a month:

AS3 doesn’t easily give you the number of days in a month, such as “April has 30 days”. However, we can use the subtracting technique from above and set a day of the month of “0″, which is the same as the last day in the previous month. So, if you wanted to get the number of days in March, you could say new Date(new Date().year, 2 + 1, 0).date;

This fills in the current year, then a month of 2 (March) + 1 (April), and then sets the day of the month to 0. In the DateUtility class, you can say DateUtility.GetDaysInMonth(monthIndex);. Remember, AS3 uses a zero-based index for months, so January is 0, December is 11; the GetDaysInMonth() function follows that standard.

Difference in days between two dates:

If you would like to find the difference between two dates, you can simply find the number of milliseconds between them with the .time parameter, and then divide that by 86400000, the number of milliseconds in one day.

var dtDate1:Date = new Date(2008, 4, 1); //May 1st, 2008
var dtDate2:Date = new Date(2008, 6, 20); //July 20th, 2008
var difference:uint = Math.floor(Math.abs((dtDate2.time - dtDate1.time) / 86400000));

We use Math.floor so that we don’t get a partial date, such as 20.2 days. Math.abs is so that you’re actually finding the difference between the days. You can of course remove that if you need to know which date is greater than the other.

Switching to a different timezone:

First of all, you need to get back to UTC, which is easy with the Date.getTimezoneOffset() function.

var dtNow:Date = new Date();
dtNow.minutes += dtNow.getTimezoneOffset();

At this point, you have UTC time, and it’ll be easy to convert to a different timezone. Let’s say we have

var timezone:Number = -7; //Mountain time

To convert, this simple math will work:
dtNow.minutes -= (convertToTimezone * 60);

However, you’re quickly going to run into Daylight Savings Time. A silly little way I get around it is with creating an arbitrary date in January and seeing if it has a timezone offset that’s greater than the date I’m working with, like so:

var blnIsDST:Boolean = (dtNow.getTimezoneOffset() < new Date(2000, 0, 1).getTimezoneOffset());

Then, to convert, you can say:
dtNow.minutes += blnIsDST ? 60 : 0;

If you would prefer, I’ve wrapped up all of the timezone switch functionality in my DateUtility.as that you can download. Download DateUtility.as »

Date Formatting

If you want a formatted date that’s more robust than dtNow.toString(), then you can use the DateFormatter class found in mx.formatters.DateFormatter.

var df:DateFormatter = new DateFormatter();
df.formatString = "EEEE, MMMM DD, YYYY @ L:NNA";
trace(df.format(new Date()));

This should print out something like “Tuesday, September 09, 2008 @ 1:41am”. You can see the full docs on the formatString parameter on the Adobe LiveDocs page.

Date()-ing Tips and Gotchas

Remember that the hours, month, and day properties are zero-based. Hour 0 is midnight, month 2 is March, and day 6 is Saturday.

Date.time is the number of milliseconds since midnight, 1/1/1970 UTC, also known as the Epoch: an arbitrary point in time used to store the value of a date. I like to use Date.time to do a quick compare to see if one Date is greater than another, as in the date difference example above.

Also, Date.date is the day of the month, while Date.day is the day of the week.

Please let me know if there’s something I’ve forgotten that you’re interested in.

Download DateUtility.as »

Comments 8 Comments »

Here’s a nice gotcha I found a while back when trying to run a filter on some XML data. If you write an attribute filter inside of a switch statement, the query will return null when you assign it to the XMLList variable. If you trace out the query instead of assigning it, it works fine.


switch (anything) {
case "testcase":
var xlData:XMLList = xmlData.Books.Book.(@author == 'Dickens');
break;
}

The above code will fail; apparently the scoping inside of switch statements is broken. There’s a bug report filed already, but it would be nice to see a fix for this.

Comments 6 Comments »