Wednesday, May 21, 2008

Awesomeness with JSEnumerable and JSArrayEnumerable

JSEnumerable and JSArrayEnumerable are included in the core JumpShip library but are powerful classes in their own right and can even be used outside of the JumpShip Framework.

The enumerable classes provide standard methods for traversing and manipulating collections of data. The methods they define are meant to mirror standard Enumerable modules in other programing languages like Ruby. A lot of the time one of the more tedious tasks of managing data is the need to iterate over the entire data set to find the specific piece of data you need to retrieve or perform a transformation on.

The two enumerable classes in JumpShip each provide the same functionality but in two different ways. JSEnumerable is a class that can be extended by your data model so that it inherits all of the enumerable functionality. JSArrayEnumerable is a static class that manipulates the prototype of the standard Array class so that all Arrays in your app gain the enumerable functionality. Even though prototype manipulation has become a frowned upon practice in AS3, the Array is the most often used class for enumeration. If you want to make extensive use of the enumerable methods, using JSArrayEnumerable can save you a lot of work by simply allowing you to use Array objects throughout your application instead of sub-classes of JSEnumerable. Either way is fine, however, and will allow you to take full advantage of the enumerable methods to do some great tricks with your data. Let's see some examples.

Let's start with some dummy data. We're using JSDataModel as our data collection. It extends JSEnumerable:

var test = new JSDataModel();

var rec1:JSDataRecord = new JSDataRecord();
var rec2:JSDataRecord = new JSDataRecord();
var rec3:JSDataRecord = new JSDataRecord();
var rec4:JSDataRecord = new JSDataRecord();

rec1.create({firstname:"joe", lastname:"smith", purchases:10});
rec2.create({firstname:"jamie", lastname:"doe", purchases:1});
rec3.create({firstname:"jamie", lastname:"scanlon", purchases:50});
rec4.create({firstname:"mickey", lastname:"mouse", purchases:16});


Let's also turn on the JSArrayEnumerable class to make all Array objects enumerable:

JSArrayEnumerable.enable = true;

Now the fun part. Let's say we want a way to quickly compile a list of all of the last names in our data set.

var names:Array = test.pluck("lastname"); // smith,doe,scanlon,mouse

The 'pluck' method pulls out a filed from the records in the collection and presents it as an Array. If you were to think of the data in the form of a table, pluck returns a column. In this case we pluck the 'lastname' column. Now lets say we want to only pull out the last names that contain the letter 's'.

var sortedNames:Array = names.grep( /s/, function(item, index) {
return item;
}); // smith,scanlon,mouse

Notice that we have called the 'grep' method on the 'names' variable which is an Array. Since we have enabled JSArrayEnumerable, all Array's now have this method. If we hadn't enabled JSArrayEnumerable, this would throw a compile error. The 'grep' method is one of the most powerful. It lets you use a RegExp ( Regular Expression ) to filter which records to return in the form of an Array. The capability to filter the records in your data set is only limited by your ability to create a good Regular Expression. The 'grep' method calls toString() on each item in the data set so it is also valuable to make sure that you implement a meaningful toString() method if you are working with complex datatypes, ( here we are only dealing with Strings and Numbers so this isn't an issue ). 

Hopefully you can begin to see the power of using the enumerable classes. We just performed two operations that would normally require a loop and at least one 'if' statement. Here's another example. Let's say we want to get a running total of all customer purchases.

var total:Number = 0;
total = test.inject(total, function(memo, item, index) {
return Number(memo) + Number(item.purchases);
}); // 77

The 'inject' method allows you to specify a variable ( in this case a Number ) that will be submitted to a function for each item in the data set. The function also gets the item in the data set and the index. Whatever is returned by the function is submitted to the next item in the data set. This is a great way to apply a recursive function to every item in the data set. In this case, we use it to tally the values of the 'purchases' field.

No comments: