Hi Everyone,
this is a follow-up to the following posts:
In this post, I will add code to identify each request uniquely, give you a class called “AbstractClient” that will handle most of the mechanics and is easily extended. The idea is to give you a base on which to build. Essentially, for every service call you will want to make you will need 2 functions:
- A public function that will send the request out
- a protected function that will receive the response, digest it and dispatch an event
For those who’d like to go ahead and enjoy the goods right away, I will write this tutorial so it can be read and understood on it’s own.
Class Member Variables
AbstractClient will contain 2 variables:
protected var _requestQueue:Array;— used to store current requestsprotected var _requestSequence:Number = 0;— used to give each request a unique identifier
So here is what it looks like so far:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | package maax.ws { import flash.events.EventDispatcher; public class AbstractClient extends EventDispatcher { protected var _requestQueue:Array; protected var _requestSequence:Number = 0; public function AbstractClient() { _requestQueue = []; _requestSequence = 0; } } } |
So the queue is initialized to an empty array, and the sequence is set to 0.
The core functionality
So the idea is to keep currently occuring requests in the queue, and to describe the request using a wrapper object. This object will contain:
- requestId
- loader (URLLoader reference) that is running it
- success flag
- any other information the caller wishes to have
The wrapper object is passed as a parameter to the public function being called from the outside. We then add information into it and put it in the _requestQueue.
When the loader (URLLoader) receives the result, we get the proper wrapper object from the queue, and then use this information as necessary.
Here is the main function, called runLoader.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | protected function runLoader(request:URLRequest, doComplete:Function, wrapper:Object):Number { var loader:URLLoader = new URLLoader(); loader.dataFormat = URLLoaderDataFormat.TEXT; loader.addEventListener(HTTPStatusEvent.HTTP_STATUS, doHttpStatus); loader.addEventListener(IOErrorEvent.IO_ERROR, doIOError); loader.addEventListener(ProgressEvent.PROGRESS, handleProgress); loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR , doSecurityError); loader.addEventListener(Event.COMPLETE, doComplete); loader.load(request); wrapper.id = _requestSequence++; wrapper.success = false; wrapper.loader = loader; _requestQueue.push(wrapper); return wrapper.id; } |
So basically the loader is created, we set the right format for the data to be sent and received, listen to all potential events. The Event.COMPLETE event handler is specified by the subclass calling this function. Then assign the wrapper properties and push it in the queue, and return the id of this request to the subclass’ function.
Error Handling
What’s left is the code to handle errors. This is, however, not fully implemented yet. First lets get a few function to retrieve the right Wrapper objects from the queue..
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | protected function getLoaderIndex(loader:URLLoader):Number { for (var i:Number = 0; i < _requestQueue.length; i++) { if (_requestQueue[i].loader == loader) return i; } return -1; } protected function getWrapper(loader:URLLoader):Object { return _requestQueue[getLoaderIndex(loader)]; } |
Next the actual error handling functions, which you will have to implement yourself..
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | protected function doHttpStatus(evt:HTTPStatusEvent):void { //trace("WSClient.doHttpStatus:" + evt.status); } protected function doIOError(evt:IOErrorEvent):void { var wrapper:Object = getWrapper(evt.target as URLLoader); if(!wrapper.success) trace("WSClient.doIOError(" + evt.toString()); else trace("success!"); } protected function doSecurityError(evt:SecurityErrorEvent):void { trace("WSClient.doSecurityError("+evt.toString()); } protected function handleIOError(evt:IOErrorEvent):void { trace("ioError on loader:" + getLoaderIndex(evt.target as URLLoader)); } |
Handling Progress
You could handle progress from a request, although I doubt that in most cases the “bytesTotal” will be know. Here is some sample code:
1 2 3 4 5 6 | protected function handleProgress(evt:ProgressEvent):void { var percent:Number = Math.round(evt.bytesLoaded / evt.bytesTotal * 100); var wrapper:Object = getWrapper(evt.target as URLLoader); wrapper.loaded = percent; } |
AbstractClient Complete Source
here is the source, complete..
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 | package newcommerce.ws { import flash.events.EventDispatcher; import flash.net.URLRequest; import flash.net.URLLoader; import flash.net.URLLoaderDataFormat; import flash.events.HTTPStatusEvent; import flash.events.IOErrorEvent; import flash.events.ProgressEvent; import flash.events.SecurityErrorEvent; import flash.events.Event; import maax.reference.Params; /** * ... * @author Martin Legris ( http://blog.martinlegris.com ) */ public class AbstractClient extends EventDispatcher { protected var _requestQueue:Array; protected var _requestSequence:Number = 0; public function AbstractClient() { _requestQueue = []; _requestSequence = 0; } protected function runLoader(request:URLRequest, doComplete:Function, wrapper:Object):Number { var loader:URLLoader = new URLLoader(); loader.dataFormat = URLLoaderDataFormat.TEXT; loader.addEventListener(HTTPStatusEvent.HTTP_STATUS, doHttpStatus); loader.addEventListener(IOErrorEvent.IO_ERROR, doIOError); loader.addEventListener(ProgressEvent.PROGRESS, handleProgress); loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR , doSecurityError); loader.addEventListener(Event.COMPLETE, doComplete); loader.load(request); wrapper.id = _requestSequence++; wrapper.success = false; wrapper.loader = loader; _requestQueue.push(wrapper); return _requestSequence - 1; } protected function getLoaderIndex(loader:URLLoader):Number { for (var i:Number = 0; i < _requestQueue.length; i++) { if (_requestQueue[i].loader == loader) return i; } return -1; } protected function getWrapper(loader:URLLoader):Object { return _requestQueue[getLoaderIndex(loader)]; } protected function doHttpStatus(evt:HTTPStatusEvent):void { //trace("WSClient.doHttpStatus:" + evt.status); } protected function doIOError(evt:IOErrorEvent):void { var wrapper:Object = getWrapper(evt.target as URLLoader); if(!wrapper.success) trace("WSClient.doIOError(" + evt.toString()); else trace("success!"); } protected function doSecurityError(evt:SecurityErrorEvent):void { trace("WSClient.doSecurityError("+evt.toString()); } protected function handleProgress(evt:ProgressEvent):void { var percent:Number = Math.round(evt.bytesLoaded / evt.bytesTotal * 100); var wrapper:Object = getWrapper(evt.target as URLLoader); wrapper.loaded = percent; } protected function handleIOError(evt:IOErrorEvent):void { trace("ioError on loader:" + getLoaderIndex(evt.target as URLLoader)); } } } |
Sample Usage
So as per Part 2, we woud like it to be a singleton. So here is the start..
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | package newcommerce.ws { /** * ... * @author Martin Legris ( http://blog.martinlegris.com ) */ public class WSClient extends AbstractClient { protected static var _instance:WSClient; public static function getInstance():WSClient { if (_instance == null) _instance = new WSClient(); return _instance; } public function WSClient() { super(); } } } |
Now, here is where the action happens..
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | public function getProductSizes(locationId:Number, lang:String = "EN"):Number { var request:URLRequest = new URLRequest("http://nowhere.com"); request.method = URLRequestMethod.GET; var vars:URLVariables = new URLVariables(); vars.locationId = locationId; vars.lang = lang; request.data = vers; var wrapper:Object = { locationId : locationId, lang : lang }; return runLoader(request, handleProductSizes, wrapper); } |
This function is public, so it can be called by the object using this WSClient. It returns a number, the unique identifier of the request. First we create the URLRequest object pointing the url of your service. Then we assign GET method to it. Then we put all the variables inside of a URLVariables object, and assign it to the request. Then create the wrapper object, giving it certain params that could be useful later and call runLoader which does the magic for us.
You will notice that runLoader receives a reference to handleProductSizes which is actually a function that we need to define like so:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | protected function handleProductSizes(evt:Event):void { // get the wrapper var wrapper:Object = getWrapper(evt.target as URLLoader); // convert received data to XML var xml:XML = new XML(evt.target.data as String); // get the locationId from the wrapper var locationId:Number = wrapper.locationId; // here you would digest the information into some kind of feed, like showed in Part 3 of the series var feed:ProductSizeFeed = new ProductSizeFeed(xml.sizes[0]); // then dispatch some event, the event contains the requestId, so anybody listening to this event can know if it was their request ... or not. dispatchEvent(new ProductSizeEvent(ProductSizeEvent.PRODUCT_SIZE_FEED_READY, feed, locationId, wrapper.id)); } |
As a bonus, here is the code for the ProductSizeEvent
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | package maax.events { import maax.feeds.ProductSizeFeed; /** * ... * @author Martin Legris */ public class ProductSizeEvent extends WSEvent { public static var PRODUCT_SIZE_FEED_READY:String = "product_size_feed_ready"; protected var _feed:ProductSizeFeed; public function get feed():ProductSizeFeed { return _feed; } public function ProductSizeEvent(type:String, feed:ProductSizeFeed, locationId:Number, requestId:Number) { _feed = feed; super(type, locationId, requestId); } } } |
Hope this helps! full source code… well you can ask; but this is straightforward, I encourage READING and TRYING and getting it to work. This is invaluable experience and cannot be bought or copy-pasted.
Cheers!
Martin