You can work with request/response data communication in three basic ways: via simple HTTP services (including REST services and services using JSON), SOAP web services, and Remoting. Each achieves the same basic goal of sending a request and receiving a response, and as such you can use them for the same purposes within Flex applications. Which method you choose depends primarily on what type of service you have available. For example, if you want to load XML data from an XML document you should use simple HTTP service communication. However, if you want to call a web service method, you should use web services communication.
The most basic type of HTTP request/response communication uses what we call simple HTTP services. These services include things such as text and XML resources, either in static documents or dynamically generated by something such as a ColdFusion page, a servlet, or an ASP.NET page. Simple HTTP services might also include pages that run scripts when called in order to do things such as insert data into or update databases or send email. You can use simple HTTP services to execute these sorts of server behaviors, to load data, or to do both.
Flex provides two basic ways in which you can call simple HTTP
services: using HTTPService
,
a Flex framework component; and using the Flash Player
class flash.net.URLLoader
.
HTTPService
is a component that
allows you to make requests to simple HTTP services such as text files,
XML files, or scripts and pages that return dynamic (or static) data.
You must always define a value for the url
property of an HTTPService
object. The url
property tells
the object where it can find the resource to which it should make the
request. The value can be either a relative URL or an absolute URL. The
following example uses MXML to create an HTTPService
object that loads text from a file
called data.txt saved in the same
directory as the compiled .swf
file:
<mx:HTTPService id="textService" url="data.txt" />
Now that you know how to create a new HTTPService
instance, let’s discuss how to
send requests, handle results, and pass parameters.
Creating an HTTPService
object does not automatically make the request to load the data.
To make the request, you must call the send()
method. You can call the send()
method in response to any framework
or user event. For example, if you want to make the request as soon as
the application initializes, you can call send()
in response to the initialize event.
If you want to load the data when the user clicks a button, you can
call the send()
method in response
to a click event:
textService.send();
The send()
method makes the
request, but a response is not likely to be returned instantaneously.
Instead, the application must wait for a result event. The result
event occurs when the entire response has been returned. The following
example displays an alert when the data loads:
<mx:HTTPService id="textService" url="data.txt" result="Alert.show('Data loaded')" />
Of course, normally you would want to do something more useful
than display an alert when the data loads. More commonly, you will
want to use the data in some way. You can retrieve the response data
(i.e., the data that has loaded) using the lastResult
property. Plain text is always loaded as string data. However, the
HTTPService
component is capable of
automatically converting serialized data into associative arrays. For
this reason, the lastResult
property is typed as Object
. If you
want to treat it as a string, you must cast it. Example 17-1 loads text from a file and
then displays it in a text area.
Example 17-1. Loading text with HTTPService
<?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:remoting=" com.oreilly.programmingflex.rpc.*" layout="absolute" initialize="initializeHandler(event)"> <mx:Script> <![CDATA[ private function initializeHandler(event:Event):void { textService.send(); } private function resultHandler(event:Event):void { textArea.text = String(textService.lastResult); } ]]> </mx:Script> <mx:HTTPService id="textService" url="data.txt" result="resultHandler(event)" /> <mx:TextArea id="textArea" /> </mx:Application>
Although you can explicitly handle the result event, it is far more common to use data binding. Example 17-2 accomplishes the same thing as Example 17-1, but it uses data binding.
Example 17-2. Using data binding with HTTPService
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:remoting="
com.oreilly.programmingflex.rpc.*" layout="absolute"
initialize="initializeHandler(event)">
<mx:Script>
<![CDATA[
private function initializeHandler(event:Event):void {
textService.send();
}
]]>
</mx:Script>
<mx:HTTPService id="textService" url="data.txt" />
<mx:TextArea id="textArea" text="{textService.lastResult}"
/>
</mx:Application>
When possible, HTTPService
will deserialize data it loads in much the same way as it would interpret
data placed in a Model
tag. For
example, consider the following data:
<countries> <country>Select One</country> <country>Canada</country> <country>U.S.</country> </countries>
If you attempt to load this data using HTTPService
, it will be parsed into an
object named countries
that
contains an array named country
,
each element of which corresponds to the <country>
elements. Example 17-3 illustrates this using a
live XML file that contains the XML data shown in the preceding code
block. It uses data binding to populate the combo box with the
data.
Example 17-3. Loading XML with HTTPService
<?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" initialize="initializeHandler(event)"> <mx:Script> <![CDATA[ private function initializeHandler(event:Event):void { countriesService.send(); } ]]> </mx:Script> <mx:HTTPService id="countriesService" url="http://www.rightactionscript.com/states/xml/countries.xml" /> <mx:VBox> <mx:ComboBox id="country" dataProvider="{countriesService.lastResult.countries.country}" /> </mx:VBox> </mx:Application>
As we’ve already seen, by default HTTPService
results are interpreted as text
if they are blocks of text, and if the results are XML data, they're
parsed into an object. However, that's merely the default behavior.
You can explicitly dictate the way in which the results are handled
using the resultFormat
property of
the HTTPService
object. The default value is object
, which yields the default behavior
you’ve already seen. You can optionally specify any of the following
values:
text
The data is not parsed at all, but is treated as raw text.
flashvars
The data is assumed to be in URL-encoded format, and it will be parsed into an object with properties corresponding to the name/value pairs.
array
The data is assumed to be in XML format, and it is parsed into objects much the same as with the object settings. However, in this case, the result is always an array. If the returned data does not automatically parse into an array, the parsed data is placed into an array.
xml
The data is assumed to be in XML format, and it is
interpreted as XML using the legacy XMLNode
ActionScript class.
e4x
The data is assumed to be in XML format, and it is
interpreted as XML using the ActionScript 3.0 XML
class (E4X).
When you want to pass parameters to the service, you can use the
request
property of the HTTPService
instance. The request
property
requires an Object
value. By
default, the name/value pairs of the object are converted to
URL-encoded format and are sent to the service using HTTP GET
. You can assign an object using
ActionScript, as in the following:
var parameters:Object = new Object(); parameters.a = "one"; parameters.b = "two"; service.request = parameters;
However, when creating an HTTPService
object using MXML, it's often
convenient to declare the parameters using MXML as well:
<mx:HTTPService id="service" url="script.php"> <mx:request> <a>one</a> <b>two</b> </mx:request> </mx:HTTPService>
Declaring the request in this way also allows you to use data
binding with the parameters. To illustrate this with a working
example, consider the code in Example 17-4, which builds on
Example 17-3 by using a second
HTTPService
object to retrieve
state names based on the selected country.
Example 17-4. Using HTTPService with input parameters
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"
initialize="initializeHandler(event)">
<mx:Script>
<![CDATA[
private function initializeHandler(event:Event):void {
countriesService.send();
}
private function changeHandler(event:Event):void {
statesService.send();
}
]]>
</mx:Script>
<mx:HTTPService id="countriesService"
url="http://www.rightactionscript.com/states/xml/countries.xml" />
<mx:HTTPService id="statesService"
url="http://www.rightactionscript.com/states/xml/states.php">
<mx:request>
<country>
{country.value}
</country>
</mx:request>
</mx:HTTPService>
<mx:VBox>
<mx:ComboBox id="country"
dataProvider="{countriesService.lastResult.countries.country}"
change="changeHandler(event)"
/>
<mx:ComboBox dataProvider="{statesService.lastResult.states.state}" />
</mx:VBox>
</mx:Application>
In the preceding example, the first combo box is populated with a list of countries. When the user selects a country from the combo box, it sends a request to the second service, a PHP script, sending the selected country as a parameter. The return value is in the following format:
<states> <state>Alabama</state> <state>Alaska</state> <!-- etc. --> </states>
As noted, by default, parameters are sent in URL-encoded format
using HTTP GET
. However, you can
adjust those settings. The contentType
property of the HTTPService
object determines the format in which the content is sent. The
default value is application/x-www-form-urlencoded
,
which sends the values in URL-encoded format. You can specify application/xml
to send the data as XML if
the service expects raw XML data:
<mx:HTTPService id="service" url="script.php" contentType="application/xml"> <mx:request> <parameters> <a>one</a> <b>two</b> </parameters> </mx:request> </mx:HTTPService>
The method
property
determines what transport method is used. The default is GET
, but you can also specify a value of
POST
, HEAD
, OPTIONS
, PUT
, TRACE
, or DELETE
, though Flash Player supports only GET
and POST
when running in a browser (AIR
applications support all methods).
Although the simplest and quickest way to use an HTTPService
object is to primarily use MXML, this technique is best-suited
to nonenterprise applications in which the data communication
scenarios are quite simple. However, for more complex data
communication requirements, it is advisable to use remote proxies, as
discussed earlier in this chapter. Because HTTPService
components provide significant
data conversion advantages (such as automatic serialization of data),
it is still frequently a good idea to use an HTTPService
object within a remote proxy.
However, it is generally necessary to then work with the HTTPService
component entirely with
ActionScript, including constructing the object and handling the
responses.
When working with HTTPService
objects entirely with ActionScript, you’ll want to import the mx.rpc.http.HTTPService
class. You can then construct an instance with a standard
new
statement:
var httpRequest:HTTPRequest = new HTTPRequest();
You should then set the url
property:
httpRequest.url = "data.txt";
Just as you would listen for any event from any object, you need
to add listeners to HTTPService
objects using addEventListener()
.
HTTPService
objects dispatch
events of type ResultEvent
when a response is returned, and they dispatch events of type FaultEvent
when an error is
returned from the server. The ResultEvent
and
FaultEvent
classes are in the
mx.rpc.events
package:
httpRequest.addEventListener(ResultEvent.RESULT, resultHandler);
Example 17-5 is a simple working
example that uses the recommended remote proxy approach in conjunction
with HTTPService
. This example accomplishes the same basic thing as
previous MXML-based examples—displaying countries and states in combo
boxes. However, this example uses several classes to accomplish this.
The first class we’ll look at is a simple data model class called
ApplicationDataModel
. Here’s the
code.
Example 17-5. ApplicationDataModel.as
package com.oreilly.programmingflex.remotedata { import mx.collections.ListCollectionView; public class ApplicationDataModel { private static var _instance:ApplicationDataModel; private var _countryNames:ListCollectionView; private var _statesNames:ListCollectionView; [Bindable] public function set countryNames(value:ListCollectionView):void { _countryNames = value; } public function get countryNames():ListCollectionView { return _countryNames; } [Bindable] public function set statesNames(value:ListCollectionView):void { _statesNames = value; } public function get statesNames():ListCollectionView { return _statesNames; } public function ApplicationDataModel() {} public static function getInstance():ApplicationDataModel { if(_instance == null) { _instance = new ApplicationDataModel(); } return _instance; } } }
In Example 17-6, we’ll define StatesService
, the remote proxy that loads XML data using HTTPService
. The class defines two service
methods: getCountries()
and
getStates()
. When the results are
returned for the service method calls, they're assigned to the data
model.
Example 17-6. StatesService.as
package com.oreilly.programmingflex.remotedata { import mx.rpc.http.HTTPService; import mx.rpc.events.ResultEvent; import mx.collections.XMLListCollection; import com.oreilly.programmingflex.remotedata.ApplicationDataModel; public class StatesService { private var _service:HTTPService; public function StatesService() { _service = new HTTPService(); _service.resultFormat = "e4x"; } public function getCountries():void { _service.addEventListener(ResultEvent.RESULT, countriesResultHandler); _service.url = "http://rightactionscript.com/states/xml/countries.xml"; _service.send(); } public function getStates(country:String):void { _service.addEventListener(ResultEvent.RESULT, statesResultHandler); _service.url = "http://rightactionscript.com/states/xml/ states.php?country=" + country; _service.send(); } private function countriesResultHandler(event:ResultEvent):void { _service.removeEventListener(ResultEvent.RESULT, countriesResultHandler); ApplicationDataModel.getInstance().countryNames = new XMLListCollection(_service.lastResult.children() as XMLList); } private function statesResultHandler(event:ResultEvent):void { _service.removeEventListener(ResultEvent.RESULT, statesResultHandler); ApplicationDataModel.getInstance().statesNames = new XMLListCollection(_service.lastResult.children() as XMLList); } } }
Example 17-7 is the MXML application that utilizes both of these classes.
Example 17-7. Using the states service proxy and application data model
<?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" creationComplete="creationCompleteHandler(event)"> <mx:Script> <![CDATA[ import mx.rpc.http.HTTPService; import mx.rpc.events.ResultEvent; import com.oreilly.programmingflex.remotedata.StatesService; import com.oreilly.programmingflex.remotedata.ApplicationDataModel; private var _statesService:StatesService; private function creationCompleteHandler(event:Event):void { _statesService = new StatesService(); _statesService.getCountries(); } ]]> </mx:Script> <mx:VBox> <mx:ComboBox id="countryMenu" dataProvider="{ApplicationDataModel.getInstance().countryNames}" change="_statesService.getStates(countryMenu.selectedLabel)" /> <mx:ComboBox dataProvider="{ApplicationDataModel.getInstance().statesNames}" /> </mx:VBox> </mx:Application>
In this example, the StatesService
instance is created, and
getCountries()
is called
immediately. The first combo box is data-bound to the countryNames
property of the data model. As
the user selects a value from the first combo box, it calls getStates()
, passing it the selected
country. The second combo box is data-bound to the statesNames
property of the data
model.
HTTPService
allows you to use
requests and handle responses to and from simple HTTP services. You can
optionally use the Flash Player class called flash.net.URLLoader
to accomplish the same tasks entirely with ActionScript,
but at a slightly lower level. Practically speaking, there is little to
no difference between using URLLoader
and HTTPService
. However, because
many developers building pure ActionScript 3 libraries will rely on
URLLoader
(and you may rely on their
code), you will likely find it useful to be familiar with how to use
URLLoader
.
The first step when working with a URLLoader
object is always to construct the
object using the constructor method, as follows:
var loader:URLLoader = new URLLoader();
Once you’ve constructed the object, you can do the following:
Send requests.
Handle responses.
Send parameters.
You can send requests using the load()
method of a URLLoader
object. The load()
method requires that you pass it a
flash.net.URLRequest
object
specifying at a minimum what URL to use when making the request. The
following makes a request to a text file called data.txt:
loader.load(new URLRequest("data.txt"));
URLLoader
objects dispatch
complete events when a response has been returned. Any return value is
stored in the data
property of the
URLLoader
object. Example 17-8 loads XML data from a URL and
handles the response.
Example 17-8. Loading XML using URLLoader
<?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" initialize="initializeHandler(event)"> <mx:Script> <![CDATA[ private var _countriesService:URLLoader; private function initializeHandler(event:Event):void { _countriesService = new URLLoader(); _countriesService.addEventListener(Event.COMPLETE, countriesCompleteHandler); _countriesService.load(new URLRequest("http://www.rightactionscript.com/states/xml/countries.xml")); XML.ignoreWhitespace = true; } private function countriesCompleteHandler(event:Event):void { var xml:XML = new XML(_countriesService.data); country.dataProvider = xml.children(); } ]]> </mx:Script> <mx:VBox> <mx:ComboBox id="country" /> </mx:VBox> </mx:Application>
When data is returned to a URLLoader
object, it's interpreted as a
string by default. In the preceding example, you can see that this is
so because the data must be converted to an XML object.
It is possible to receive binary data and URL-encoded data in
response to a URLLoader
request. If
you want to handle a binary response, you must set the dataFormat
property of the URLLoader
object to flash.net.URLLoaderDataFormat.BINARY
. Binary
data will then be interpreted as a ByteArray
. If the returned data is in
URL-encoded format, you can set the dataFormat
property to flash.net.URLLoaderDataFormat.VARIABLES
and
the returned data will be interpreted as a flash.net.URLVariables
object. URLVariables
objects contain properties corresponding to the name/value pairs
in the returned value. For example, if a URLLoader
object is set to handle
URL-encoded return data, a return value of a=one&b=two
will create a URLVariables
object with a
and b
properties accessible, as in the following:
trace(loader.data.a + " " + loader.data.b);
You can send parameters using URLLoader
as well. To send parameters, you
assign a value to the data
property
of the URLRequest
object used to make the request. The URLRequest
object can send binary data or
string data. If you assign a ByteArray
to the data
property it's sent as binary. If you
assign a URLVariables
object to the
data
property, the data is sent in
URL-encoded format. Otherwise, the data is converted to a string.
Example 17-9 builds on Example 17-8 to send a parameter when
requesting state data.
Example 17-9. Sending parameters with URLLoader
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"
initialize="initializeHandler(event)">
<mx:Script>
<![CDATA[
private var _countriesService:URLLoader;
private var _statesService:URLLoader;
private function initializeHandler(event:Event):void {
_countriesService = new URLLoader();
_countriesService.addEventListener(Event.COMPLETE,
countriesCompleteHandler);
_countriesService.load(new
URLRequest("http://www.rightactionscript.com/states/xml/countries.xml"));
_statesService = new URLLoader();
_statesService.addEventListener(Event.COMPLETE,
statesCompleteHandler);
XML.ignoreWhitespace = true;
}
private function countriesCompleteHandler(event:Event):void {
var xml:XML = new XML(_countriesService.data);
country.dataProvider = xml.children();
}
private function statesCompleteHandler(event:Event):void {
var xml:XML = new XML(_statesService.data);
state.dataProvider = xml.children();
}
private function changeHandler(event:Event):void {
var request:URLRequest = new
URLRequest("http://www.rightactionscript.com/states/xml/states.php");
var parameters:URLVariables = new URLVariables();
parameters.country = country.value;
request.data = parameters;
_statesService.load(request);
}
]]>
</mx:Script>
<mx:VBox>
<mx:ComboBox id="country" change="changeHandler(event)"
/>
<mx:ComboBox id="state" />
</mx:VBox>
</mx:Application>
You can use the method
property to specify how the data should be sent. Possible values are
flash.net.URLRequestMethod.POST
and
flash.net.URLRequestMethod.GET
.
Now that we’ve had a chance to see the basics of working with
URLLoader
, here’s an example that
uses URLLoader
in context. In Using HTTPService with ActionScript" you saw a complete
working example. You can use the same MXML document and data model
class and make a few minor edits to the remote proxy class to use
URLLoader
instead of HTTPService
. Example 17-10 is the new remote proxy
class.
Example 17-10. The new StatesService.as
package com.oreilly.programmingflex.remotedata { import flash.net.URLLoader; import flash.net.URLRequest; import flash.events.Event; import flash.net.URLVariables; import mx.collections.XMLListCollection; import com.oreilly.programmingflex.remotedata.ApplicationDataModel public class StatesService { private var _service:URLLoader
; public function StatesService() { _service = newURLLoader
(); } public function getCountries():void { _service.addEventListener(Event.COMPLETE
, countriesResultHandler); var request:URLRequest = new URLRequest("http://rightactionscript.com/states/xml/countries.xml"); _service.load(request); } public function getStates(country:String):void { _service.addEventListener(Event.COMPLETE
, statesResultHandler); var request:URLRequest = new URLRequest("http://rightactionscript.com/states/xml/states.php"); var parameters:URLVariables = new URLVariables(); parameters.country = country; request.data = parameters; _service.load(request); } private function countriesResultHandler(event:Event
):void { _service.removeEventListener(Event.COMPLETE
, countriesResultHandler); ApplicationDataModel.getInstance().countryNames = new XMLListCollection(new XML(_service.data)
.children() as XMLList); } private function statesResultHandler(event:Event
):void { _service.removeEventListener(Event.COMPLETE
, statesResultHandler); ApplicationDataModel.getInstance().statesNames = new XMLListCollection(new XML(_service.data)
.children() as XMLList); } } }
You can test this new StatesService
class using the same MXML
document and ApplicationDataModel
class from
earlier in the chapter.