Data access with array extending and proxy extending

| 7 min read

I made some experiments on how to easily access to data in a Flash site. It is probably a mix with good and bad ways but I think it is worth having a look.

What I’ve done here is get data with syntax that is similar to what you get using E4X in AS3. For example with XML and E4X you can use syntax like that:

xml..item[2].author.name.text();

I’m not saying that this experiment is a good practice but I want to show you what you can do by extending the Array class and extending the Proxy class in AS3. The Proxy class is the replacement of the __resolve we were using in AS2.

Let’s say we have a website that will load data from a database at the start, for example using AMFPHP or whatever, and we are going to fill this data at run-time.

In my example I have a kind of tree with countries > cities > people.

I would like to be able to loop easily through the data, as well as getting them with a friendly/readable syntax if needed.

In my example, I have a Singleton class that contains all the data. I don’t load the data in my example, I statically fill the class. I will access to the data like that:

Content.data

This will return me a array of data, which will contains an array of countries, each instance of the country array will contains an array of cities, which will contains an array of people, etc…

So I’d like to access to my data like this:

To browse the countries:

Content.data[0] Content.data[1]

To browse the cities in the countries:

Content.data[0][0] Content.data[0][1]

To browse the people in the cities:

Content.data[0][0][0] Content.data[0][0][1]

That’s nice to loop through with a “for each” or whatever. An Array is a pretty nice tool to store data as you get some functions to manage it like push, splice, reverse, etc.

But these arrays have a problem, what if I need to get more info on my countries, like the weather, an ID or the surface of the country? I would like access to more data like that:

Content.data[0].name Content.data[0].id

This is possible with an Array as it is one of the few classes in AS3 that are dynamic. It means you can set any property at run time. For example MovieClip is dynamic and Sprite is not, try to add properties at run-time on these classes to see the result. Some people are saying that is it a good and bad things. It is easy to use but you don’t know what kind of properties will be added in your Array at run-time.

So you don’t want that, and better, you want to control what kind of data they are going to put in your Array. We will get that by extending the Array class.

There’s a nice explanation here to extends an Array, as well as control the data type your custom Array class will accept.

So far it is not bad, we can easily access to our data, we can have properties on these arrays and finally we know that we will have the right type of content inside.

The other thing I want is access to my data with a nice E4X-like:

Content.data.France.name Content.data.France.Paris.name

Easy, we just need a property France on this array and it is done. Well, the thing is we don’t know yet what this array will be filled with, so we are talking about dynamic properties. These dynamic properties will have to be added to the array when we create it because we said we want to control and we don’t want people adding their own properties.

How do we do that?

We’re going to use the Proxy class that make able us able to handle dynamic properties and dynamic calling.

We will have a class that will be an array (what you get when you call Content.data), a class for the countries, a class for the cities and a class for the people. The people class won’t be an array.

So we have the classes DataArray, CountryArray and CityArray that are extending the Array class. We will also have a Proxy class (a class that is extending the Proxy one) for each of the Array. I choose to a Proxy class for each Array class as it is easier to understand.

Let’s see a bit of code.

The Singleton Content class

package com.soundstep.data {

/**
* <b>Author:</b> Romuald Quantin - <a href="http://www.soundstep.com/" target="_blank">www.soundstep.com</a><br />
* <b>Class version:</b> 1.0<br />
* <b>Actionscript version:</b> 3.0<br />
* <b>Copyright:</b> Free to use and change (except to include in a framework), an notification email will be welcome for a commercial use (just for information).<br />
* <b>Date:</b> 05-2008<br />
* <b>Usage:</b> Manage a Section
* @example
* <listing version="3.0">

* </listing>
*/


public class Content {

//------------------------------------
// private properties
//------------------------------------

private static var content:Content = new Content();
private static var _data:Data;

//------------------------------------
// public properties
//------------------------------------



//------------------------------------
// constructor
//------------------------------------

public function Content() {
if (content) throw new Error("Content is Singleton and can only be accessed through Content.data");
if (_data == null) buildData();
}

//
// PRIVATE, INTERNAL
//________________________________________________________________________________________________

private function buildData():void {
_data = new Data();

}

//
// PUBLIC
//________________________________________________________________________________________________

public static function get data():Data {
return _data;
}

}

}

The Data and DataArray class

The DataArray is extending the Array class. To be able to extend the Array class you need the dynamic keyword before the class.

dynamic public class DataArray extends Array {

We have a private property type Class used to control the Data we will push in our extended Array.

private var dataType:Class;

To control the type pushed we have to overwrite 4 functions: push, concat, splice and unshift, as it is nicely explained in the AS3 documentation.

Here is the code:

package com.soundstep.data {

/**
* <b>Author:</b> Romuald Quantin - <a href="http://www.soundstep.com/" target="_blank">www.soundstep.com</a><br />
* <b>Class version:</b> 1.0<br />
* <b>Actionscript version:</b> 3.0<br />
* <b>Copyright:</b> Free to use and change (except to include in a framework), an notification email will be welcome for a commercial use (just for information).<br />
* <b>Date:</b> 05-2008<br />
* <b>Usage:</b> Manage a Section
* @example
* <listing version="3.0">

* </listing>
*/


dynamic public class DataArray extends Array {

//------------------------------------
// private properties
//------------------------------------

private var dataType:Class;

//------------------------------------
// public properties
//------------------------------------

//------------------------------------
// constructor
//------------------------------------

public function DataArray(...args) {
dataType = Country;
for (var i:int=0; i<args.length; i++) this.push(args[i]);
length = length;
}

//
// PRIVATE, INTERNAL
//________________________________________________________________________________________________

//
// PUBLIC
//________________________________________________________________________________________________

AS3 override function push(...args):uint {
for (var i:* in args) {
if (!(args[i] is dataType)) {
trace("Error: you must push an Object type Country");
args.splice(i,1);
}
}
return super.push.apply(this, args);
}

AS3 override function concat(...args):Array {
var passArgs:DataArray = new DataArray();
for (var i:* in args) passArgs.push(args[i]);
return super.concat.apply(this, passArgs);
}

AS3 override function splice(...args):* {
if (args.length > 2) {
for (var i:int=2; i< args.length; i++) {
if (!(args[i] is dataType)) args.splice(i,1);
}
}
return super.splice.apply(this, args);
}

AS3 override function unshift(...args):uint {
for (var i:* in args) {
if (!(args[i] is dataType)) args.splice(i,1);
}
return super.unshift.apply(this, args);
}

}

}

To create a new DataArray instance, we actually don’t use the DataArray class directly but his Proxy, that will handle the dynamic properties.

I named the class Data, it is what you get with Content.data.

To extend the Proxy class you need to import the class itself and its namespace:

import flash.utils.Proxy; import flash.utils.flash_proxy;

Proxy is also a dynamic class:

dynamic public class Data extends Proxy {

We have our private var that will be our DataArray instance:

private var _item:DataArray;

To control the properties and method called through our Proxy, we need to override 3 methods (with the namespace flash_proxy):

override flash_proxy function callProperty(methodName:*, ... args):* { override flash_proxy function getProperty(name:*):* { override flash_proxy function setProperty(name:*, value:*):void {

When you will call an unknown method, the Proxy class invoke callProperty, and when you will call or set a property (like Content.data.France), the Proxy class invoke getProperty and setProperty.

In my example I trace an Error if you try to set your own property, this is our control. When you get a property, I loop through the DataArray instance to see if I’ve got an item with that name. We could do it with the id or any property of your choice (that is in the DataArray class), and if yes I return it. This is the instance you get by using Content.data.France.

So in brief, when I call Content.data.France, I’m going through the Proxy that is basically managing the dynamic properties added at run-time. The DataArray class is extending the Array class to control the properties and data type.

Here is the code of the Data class:

package com.soundstep.data {

/**
* <b>Author:</b> Romuald Quantin - <a href="http://www.soundstep.com/" target="_blank">www.soundstep.com</a><br />
* <b>Class version:</b> 1.0<br />
* <b>Actionscript version:</b> 3.0<br />
* <b>Copyright:</b> Free to use and change (except to include in a framework), an notification email will be welcome for a commercial use (just for information).<br />
* <b>Date:</b> 05-2008<br />
* <b>Usage:</b> Manage a Section
* @example
* <listing version="3.0">

* </listing>
*/


import flash.utils.Proxy;
import flash.utils.flash_proxy;

dynamic public class Data extends Proxy {

//------------------------------------
// private properties
//------------------------------------

private var _item:DataArray;

//------------------------------------
// public properties
//------------------------------------

//------------------------------------
// constructor
//------------------------------------

public function Data(...args) {
_item = new DataArray();
for (var i:int=0; i<args.length; i++) _item.push(args[i]);
}

//
// PRIVATE, INTERNAL
//________________________________________________________________________________________________

private function getString():String {
var s:String = "";
for (var i:uint=0; i<_item.length; i++) {
s += "index:" + i + ", id:" + _item[i].id + ", name:" + _item[i].name;
if (i < _item.length-1) s += "\n";
}
if (_item.length == 0) return _item.name + " is empty";
else return s;
}

//
// PUBLIC
//________________________________________________________________________________________________

override flash_proxy function callProperty(methodName:*, ... args):* {
var res:*;
switch (methodName.toString()) {
case 'clear':
_item = new DataArray();
break;
case 'list':
trace(getString());
break;
default:
res = _item[methodName].apply(_item, args);
break;
}
return res;
}

override flash_proxy function getProperty(name:*):* {
for each (var i:* in _item) {
if (name == i.name) {
return i;
break;
}
}
return _item[name];
throw new Error("ERROR: Property " + name + " not found!");
}

override flash_proxy function setProperty(name:*, value:*):void {
trace("ERROR: You can't set your own property!");
}

public function toString():String {
return getString();
}
}
}

Basically the cities and countries classes are doing the same, the dataType and properties are different, a country is asking a City type and the a city is asking a People type.

For example the CountryArray class asks an id and name to create the array, you have to use a syntax like:

var france:Country = new Country(0, "France");

As it is an Array extended you can still pass more arguments (but they have to be the right type), the type here is City, that is also an Array extended but a city instance is asking an id, a name and weather as parameters:

var france:Country = new Country(0, "France", new City(0, "Paris", "As bad as London!"), new City(1, "Marseille", "More sun than Paris"));

Here is how I fill the Content with some examples:

//country
var france:Country = new Country(0, "France");
Content.data.push(france);
var uk:Country = new Country(1, "UK");
Content.data.push(uk);

// city
var paris:City = new City(0, "Paris", "As bad as London!");
france.push(paris);
france.myvar = "qwe";
var marseille:City = new City(1, "Marseille", "More sun than Paris");
france.push(marseille);
var london:City = new City(0, "London", "Quite bad!");
uk.push(london);
var liverpool:City = new City(1, "Liverpool", "Even worst than London");
uk.push(liverpool);

// people
var franck:People = new People(0, "Franck", "Dupont", 29);
paris.push(franck);
var stephane:People = new People(1, "Stephane", "Durand", 42);
paris.push(stephane);
var john:People = new People(0, "John", "Doe", 35);
london.push(john);
var david:People = new People(0, "David", "Doe", 37);
london.push(david);

And finally here is some example to access to your data:

Content.data.length
Content.data.France.length
Content.data[0].name
Content.data.France.name
Content.data.France[0].name
Content.data.France.Paris.name
Content.data.France.Paris.Franck.name
Content.data[0][0][0].name

I didn’t optimize/clean the classes and as I said, I’m not saying that it is a good way of accessing data in Flash, but it shows you how to control data type, how to extend an Array, how to extend the Proxy class and how to handle properties and methods of a class at low-level.

Download the source.