Hi Everyone,
This is the third installment of the series, you can find the first two here:
Part 1 – http://blog.martinlegris.com/?p=87
Part 2 – http://blog.martinlegris.com/?p=90
Today I will develop on the data handling once the data is received from the webservice calls.
I will describe some methodologies I’ve developped that help mainstream data handling by using custom data types to hold the data, and custom iterators to access these data, and custom event types to transport the data.
Starting Point
In the last tutorial, we had a Singleton class calls WSClient which ran the request for us, then dispatched an event once the xml data was received.
Let’s look at a sample XML file returned by a theoretical webservice. The webservice returns a list of songs contained in a particular playlist. Here is the structure for each song:
[xml]
[/xml]First thing to do here, is to create a data object to hold this data.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | package data { public class TrackData { protected var _id:Number; protected var _title:String; protected var _creator:String; protected var _location:String; protected var _info:String; protected var _image:String; protected var _tn:String; protected var _type:String; protected var _rating:Number; public function TrackData(id:Number = -1, title:String = "", creator:String = "", location:String = "", info:String = "", image:String = "", tn:String = "", type:String = "", rating:Number = 0) { _id = id; _title = title; _creator = creator; _location = location; _info = info; _image = image; _tn = tn; _type = type; _rating = rating; } } } |
Now let’s add getter/setters for the properties
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | // ... protected var _rating:Number; public function get id():Number { return _id; } public function set id(value:Number):void { _id = value; } public function get title():String { return _title; } public function set title(value:String):void { _title = value; } public function get creator():String { return _creator; } public function set creator(value:String):void { _creator = value; } public function get location():String { return _location; } public function set location(value:String):void { _location = value; } public function get info():String { return _info; } public function set info(value:String):void { _info = value; } public function get image():String { return _image; } public function set image(value:String):void { _image = value; } public function get tn():String { return _tn; } public function set tn(value:String):void { _tn = value; } public function get type():String { return _type; } public function set type(value:String):void { _type = value; } public function get rating():Number { return _rating; } public function set rating(value:Number):void { _rating = value; } public function TrackData(....) |
Now let’s create a function which will convert the XML into our TrackData object.. I usually like to put this function inside of the data object itself, and make it static. I like to call it “fromXML”.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | // ... public static function fromXML(xml:XML):TrackData { var data:TrackData = new TrackData(); data.id = parseInt(xml.id.text()); data.title = xml.title.text(); data.location = xml.location.text(); data.info = xml.info.text(); data.image = xml.image.text(); data.tn = xml.tn.text(); data.type = xml.type.text(); data.rating = parseFloat(xml.rating.text()); return data; } // .... |
To use this function we would do like so :
1 2 3 | var xml:XML = XML(evt.target.data as String); var trackData:TrackData = TrackData.fromXML(xml); |
And tada, we have “digested XML” to play with, a data object which is hard typed and easy to manipulate using auto-completion.
The Feed’s Signature
Now, since in this case we are receiving a list, or sequence of songs contained inside a playlist, it would be nice to have a custom iterator to let us easily access the songs. You can also put them in an array, but since arrays in AS3 are not type’able, (they will be in AS4), I like to create custom iterators that I call feeds. Our feed will have the following signature:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | package feeds { public class TrackFeed { // access to it's pointer public function get pointer():Number // access to the length of the trackfeed public function get length():Number // the constructor, which receives xml.. public function TrackFeed(data:XML) // some of the standard iterator functions public function first():TrackData public function next():TrackData public function last():TrackData public function previous():TrackData // to get a specific one.. public function getAt(index:Number):TrackData // other functions which could be useful public function getById(id:Number):TrackData // and in this case, a playlist could have a name, an author and a type public function get name():String public function get author():String public function get type():String } } |
Basically, what I call a Feed is an Iterator that has been adapted to a specific data type, and which lets you iterate through a list of data objects. In this case, I use it to parse XML from a webservice response and enable easy access to that data.
XML Data to be parsed
Our werservice returns a playlist. Here is what the xml looks like
[xml]
There are two techniques I’ve identified to handle the data inside of the Feed. Either transform all the data right away to custom data types, or keep the data as XML and convert it on demand. I will, in this example, show the second method. I has proven to cause problems on large data sets, for example when receiving 80k + XML files from the Amazon Web Services. So if you do have problems, just convert everything to AS3 objects and don’t keep a reference to the XML.
The Feed’s Implementation
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 | package feeds { import data.TrackData; public class TrackFeed { protected var _pointer:Number = 0; protected var _feed:XML; public function get length():Number { return _feed.trackList.track.length(); } public function get pointer():Number { return _pointer; } public function TrackFeed(data:XML) { _feed = data; } public function first():TrackData { return getAt(_pointer = 0); } public function next():TrackData { if(_pointer >= count) { _pointer = 0; return null; } return getAt(_pointer++); } public function last():TrackData { return getAt(_pointer = length-1); } public function previous():TrackData { if(_pointer <= -1) { _pointer = length-1; return null; } return getAt(--_pointer); } public function get name():String { return _feed.name.text(); } public function get type():String { return _feed.@type.text(); } public function getAt(index:Number):TrackData { if(index >= count || index < 0) return null; else { return TrackData.fromXML(_feed.trackList.track[index]); } } // returns null if not found public function getById(id:Number):TrackData { var oldPointer:Number = _pointer; first(); var data:TrackData; while(data = next()) { if(data.id == id) break; } _pointer = oldPointer; return data; } } } |
As you can see, one of the class variables is _feed which is of type XML. Basically I keep the xml, and when needed I convert the data to TrackData for easy manipulation.
To use this feed, you would do like so :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | // provided we are in the onComplete handler of the WS call, and the "evt" is of type Event. var xml:XML = XML(evt.target.data as String); var feed:TrackFeed = new TrackFeed(xml); trace("playlist is of type:"+feed.type); trace("playlist is called:"+feed.name); trace("playlist has "+feed.length+" elements); var track:TrackData; while(track = feed.next()) { trace("track ["+track.id+"] is "+track.title"); trace("track url is :"+track.location); // and so on.. } |
As you see, it really easy! It requires a bit of coding up front, but down the line it’s much clearer and simpler to use than Arrays or using data straight out of the XML.
The Custom Event
Now, from the function inside of the WSClient, which handles the Loader's Event.COMPLETE event, it would be nice to fire an event to carry this feed to all interested parties. Here is the implementation of the event:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | package events { import flash.events.Event; import feeds.TrackFeed; public class PlaylistEvent extends Event { public static const PLAYLIST_RECEIVED:String = "playlist_received"; protected var _feed:TrackFeed; public function get feed():lib.musicnation.feeds.TrackFeed { return _feed; } public function PlaylistEvent(type:String, feed:TrackFeed) { super(type); _feed = feed; } } } |
To dispatch this event from the WSClient:
1 2 3 4 5 | // provided we are in the onComplete handler of the WS call, and the "evt" is of type Event. var xml:XML = XML(evt.target.data as String); var feed:TrackFeed = new TrackFeed(xml); dispatchEvent(new PlaylistEvent(PlaylistEvent.PLAYLIST_RECEIVED, feed)); |
That’s it for part 3 folks. In part 4 I will explain how to uniquely identify each request so that all multiple classes can use the WSClient singleton and ensure they do get THEIR results, and not the results asked for by another class.
Cheers!
Hi Martin,
Your tutorials are owesome sources for Flash knowledge. I’ve a little request that I’m sure I’m not the first one to ask you for: could you provide a link of all your packages of classes for dowload? I’m not able to make your code working, maybe because I don’t create the packages structure the right way.
Can you help?
Thanks.
Hi,
THis is about a year late
, but I thought I should put up a reply. First of all. The tutorials are wonderful.
. There are two classes which you could have used to clean up the code a bit. XMLEncoder en XMLDecoder. Two nice classes that can do the marshalling and unmarshalling for you instead of that handcoded fromXML() function.
Now for the comment part
Kind regards