Advertisements
|
||
|
The first part of this article showed how Flex components can progressively enhance a Web application: The open-source SWFObject library simplifies embedding the Flash Player—a Flex application's execution environment—into HTML[1]. Flash Player, in turn, allows you to delegate UI-related logic to a Flex component[2]. Flex's UI-specific domain language not only makes UI coding simpler, but your application will benefit from Flash Player's just-in-time compiler, UI effects, animation, support for multimedia, and so forth. In addition, because Flex supports CSS-based styling, your Flex component will also blend nicely in the surrounding HTML page.
A crucial aspect of progressive enhancement with Flex rests on the ability to pass data to a Flex component. In Part I, a server-generated JSON array represented the application's data, which was then passed as a FlashVar to the Flex component.
While FlashVars allow you to incorporate Flex into an existing enterprise Web app with minimal changes to the application, FlashVars have a major limitation: Because FlashVars are name/value strings, you are limited to the maximum length of String object allowed in the browser. For most browsers, that's about 65KB.
The examples in this article can be compiled and run either with Flex Builder 3, Adobe's Eclipse-based development environment, or with the open-source Flex SDK.
A 60-day free trial version of Flex Builder 3 can be downloaded from Adobe. If you already have Eclipse installed, you can download the Eclipse plug-in version of Flex Builder 3; otherwise, choose the full Eclipse install.
You can remove that limitation by arranging for the Flex component to perform its own data loading. This entails two loading phases for the page:
The two-phase loading pattern is common to many rich-client applications, and can enhance user experience by reducing response time: As long as the user stays on the same HTML page, phase I is performed only once. All subsequent data access occurs through the second phase, alleviating the need for the browser to retrieve and render the UI anew with each subsequent request to the server. The performance benefits of this pattern are fully exploited in page-per-application type rich UIs, such as Google's GMail and Maps applications.
The rest of this article presents three ways in which Flex optimizes data loading from remote network resources: loading JSON data via HTTP, loading XML via HTTP, and directly invoking server-side Java objects through a high-performance serialization protocol. The first two techniques are a good choice for RESTful data access, while remote object invocation suits the RPC communication style[4]. An advantage of the latter approach is that client and server can communicate through strongly typed objects.
Flex's HTTPService
class simplifies the task of delegating JSON data loading to the Flex client[5]. The following ActionScript code fetches the specified URL's contents as part of the Flex client's completion event handler:
... private function onCreationComplete():void { var http:HTTPService = new HTTPService(); http.url = "booksInventory.json"; http.addEventListener(ResultEvent.RESULT, onResult); http.addEventListener(FaultEvent.FAULT, onFault); http.send(); } private function onResult(event:ResultEvent):void { booksInventory.dataProvider = JSON.decode(event.result as String) as Array; } private function onFault(event:FaultEvent):void { Alert.show("Can't load data: " + event.message); } ...
HTTPService
, part of the Flex SDK, provides analogous functionality to the Ajax XMLHttpRequest
object: Given a URL, it enables you to asynchronously fetch data from that HTTP data source. The call to send()
returns immediately. A call-back mechanism invokes a function when the result arrives or, alternatively, when an error is detected.
The example's result and failure handler functions are referenced by name: The compiler will locate methods matching the name and required parameters and return types, and assign those functions as handlers to process the result or failure of the HTTP request.
Since the example's result and failure handler functions each consists of a single line, you can make the code a bit more concise by using ActionScript function literals[6]:
private function onCreationComplete():void { var http:HTTPService = new HTTPService(); http.url = "booksInventory"; http.addEventListener(ResultEvent.RESULT, function(event:ResultEvent):void { booksInventory.dataProvider = JSON.decode(event.result as String) as Array; }); http.addEventListener(FaultEvent.FAULT, function(event:FaultEvent):void { Alert.show("Can't load data: " + event.message); }); http.send(); }
ResultEvent
's result
property is an untyped object, and we must cast it to a String before converting it into a JSON array. The as
cast expression ensures that the data is either cast into the specified data type, or null
is returned. In this example, the error handler merely shows a somewhat unfriendly box, indicating the cause of the communication error.
With this change, we can now remove the FlashVar from the Flex component's surrounding HTML page. Because the Flex component performs its own data loading, the amount of data the client can load is limited only by the available memory.XML over HTTP
While JSON is quickly emerging as the de facto standard format for HTTP-based data access, XML, although more verbose, sometimes offers advantages over JSON. Among the most important XML benefits is strong client-side API support for XML processing. Although all the recent browsers provide high-performance XSLT processing APIs, ActionScript and, by extension Flex, goes a step further: In ActionScript, XML is a native data type.
You can declare an XML literal in ActionScript thus. The type parameter indicates that x
is of type XML
:
var x:XML = <aTag> <anotherTag>Some text</anotherTag> </aTag>
ActionScript uses E4X for XML processing. E4X is an XML processing DSL developed as part of the EcmaScript standard[7]. E4X is a superset of XPath, but is not as powerful&#&8212;nor as complex—as XQuery[8][9]. If you're comfortable writing XPath expressions, the E4X syntax will be very familiar.
In addition to the XML
type and E4X, XML support in Flex includes a special-purpose XML collections API, data binding inside XML data, and numerous XML processing methods on literal XML values as well as variables.
Flex's deep XML support allows us to further reduce the amount of client code if we access XML data over HTTP. We will fetch following XML document from the server:
<?xml version="1.0" encoding="utf-8"?> <booksInventory> <book> <Title>Programming in Scala</Title> <Authors>Odersky, Martin. Lex Spoon. Bill Venners</Authors> <Year>2008</Year> <Publisher>Artima Press</Publisher> <Price>49.99</Price> <Stock>1511</Stock> </book> <book> <Title>Essential ActionScript 3</Title> <Authors>Moock, Colin</Authors> <Year>2007</Year> <Publisher>O'Reilly</Publisher> <Price>42.95</Price> <Stock>2500</Stock> </book> <book> <Title>Programming Erlang</Title> <Authors>Armstrong, Joe</Authors> <Year>2007</Year> <Publisher>Pragmatic</Publisher> <Price>32.95</Price> <Stock>3591</Stock> </book> </booksInventory>
Loading the above XML document into our Flex application requires only two minor changes: First, we specify e4x
as the request's result data type, and then we assign the array of XML book
elements as the data grid's data provider:
private function onCreationComplete():void { var http:HTTPService = new HTTPService(); http.url = "booksInventory.xml"; http.resultFormat = "e4x"; http.addEventListener(ResultEvent.RESULT, function(event:ResultEvent):void { booksInventory.dataProvider = event.result.book; }); http.addEventListener(FaultEvent.FAULT, ... http.send(); }
The expression event.result.book
is an E4X XML path expression that returns an array of XML book
objects.
Because XML nodes become properties of the XML object, no changes are needed for the table column specification in the example. The following still works:
<mx:DataGridColumn dataField="Title"/>
(Notice, however, that we had to change the Author(s)
property to Authors
, as parenthesis cannot form parts of XML element names.)
With typed XML
elements populating the data grid, you can take advantage of various XML-related facilities in Flex, such as an XML-specific collection object, XMLListCollection
. XMLListCollection
, and its non-XML sibling, ArrayCollection
, expose rich list interfaces, providing filtering and sorting, among features.
To illustrate the concept of client-side data filtering, we implement the table filter based on the author's name: As a user types, for example, the letter O into the "Find by author" box, the table displays only Programming in Scala, whose author's last name, Odersky, starts with the letter "O":
The following code shows the implementation of the filter UI panel in MXML (this code goes in the MXML section of the source):
<mx:HBox width="100%" paddingLeft="12" verticalAlign="middle" paddingTop="4" paddingBottom="4"> <mx:Label text="Find by publisher:" fontWeight="bold"/> <mx:ComboBox id="publisher_Search"/> <mx:Label text="Find by author:" fontWeight="bold"/> <mx:TextInput id="authorName" change="xmlData.refresh()"/> </mx:HBox> <mx:DataGrid dataSource="{xmlData}" ...
The panel is defined inside an HBox
container that lays out its children horizontally. Note that we assign a change
event listener to the text box with the ID value of authorName
. This handler function, which is defined here inline, is invoked by the Flex runtime whenever the user types some text in the box. Note also that both the event handler and the data grid's data source now refer to an xmlData
variable.
That variable is an XMLListCollection
that we declare and populate with XML data (this code goes between the Script
tags in the source file):
[Bindable] private var xmlData:XMLListCollection = new XMLListCollection(); private function onCreationComplete():void { xmlData.filterFunction = filterByAuthor; var http:HTTPService = new HTTPService(); http.url = "booksInventory.xml"; http.resultFormat = "e4x"; http.addEventListener(ResultEvent.RESULT, function(event:ResultEvent):void { xmlData.source = event.result.book as XMLList; }); http.addEventListener(FaultEvent.FAULT, ... http.send(); }
Note that xmlData
sports a [Bindable]
annotation, and that the grid's data source refers to xmlData
inside curly braces:
<mx:DataGrid dataSource="{xmlData}" ...
Referring to a variable inside curly braces in MXML indicates that the variable is a source for data binding. When the Flex compiler sees this notation, it generates appropriate data binding code. As a result, any changes made to the xmlData
variable, including to the contents of the collection, notify the binding targets—the data grid, in this case—of those changes.
Implementing filtering on the xmlData
collection again reveals ActionScript's functional nature:
private function onCreationComplete():void { xmlData.filterFunction = filterByAuthor; var http:HTTPService = new HTTPService(); ... } private function filterByAuthor(obj:Object):Boolean { return obj.Authors.text().search( new RegExp("^" + authorName.text.toUpperCase() + ".*") ) > -1; }
The collection's filterFunction
property is assigned a function that does the actually filtering. Each collection element is passed to this function, which must return a boolean value. This example matches each collection element's Authors
property—remember that the collection element is an XML object—against what the user entered in the author name text box.
The examples thus far fetched untyped objects from the server: the property data types of the JSON and XML objects were not specified. Flex follows some heuristics when dealing with untyped data, and many Flex applications can get away with relying on duck typing.
More sophisticated client-side data, however, is easier to work with when strongly-typed data is fetched from the server. That is especially the case when the server-side data source already defines a strongly-typed object model with, for example, Java classes.
Similar to Java, ActionScript also supports strongly typed objects. In such objects, every property, or field, has a well-defined type at compile time. Several frameworks allow a Flex application to exchange strongly-typed objects with a server. One popular tool for that task is BlazeDS[10].
BlazeDS is developed as an open-source project, but it also forms the foundation of Adobe's Flex Data Services product. BlazeDS consists of two main components: a serialization framework between Java and ActionScript objects, and a messaging framework between Flex and enterprise Java applications.
BlazeDS's serialization framework is an implementation of the Action Message Format (AMF) protocol, a binary, on-the-wire serialization protocol for Flash Player[11]. AMF is rather intelligent about dealing with circular references, as well as with resolving object types. Being a binary protocol also means that AMF minimizes network overhead.
Although many BlazeDS serialization aspects can be customized, BlazeDS's defaults are reasonable for most applications. Serialization between ActionScript and Java classes requires just four steps:
publisher
field, the corresponding ActionScript class should also have a similarly named property of the same type (Publisher
).RemoteClass
annotation.For example, the server-side object model of this example may consist of Book
and Publisher
Java classes, along with an InventoryManager
data access object:
public class Publisher { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } } public class Book { private String title; private String authors; private int year; private double price; private int stock; private Publisher publisher; public String getTitle() {return title;} public void setTitle(String title) {this.title = title;} ... } public class InventoryManager { public List<Book> getCurrentInventory() { // Fetch inventory from database } }
Because BlazeDS's requirements on server-side objects are easily satisfied in most enterprise applications, providing remote access to server-side Java objects from Flex clients can be accomplished with minimal changes to a Java Web application. (Remote object invocation with BlazeDS is not limited to server-side objects defined in Java: A forthcoming article will describe how to invoke any JVM-hosted object from Flex, and will illustrate that with an example using Scala objects on the server.)
The corresponding ActionScript classes are defined as follows:
package { [RemoteClass(alias="Publisher")] public class Publisher { public var name:String; } } package { [RemoteClass(alias="Book")] class Book { public var title:String; public var authors:String; public var year:Int; public var price:Number; public var stock:Int; public var publisher:Publisher; } }
Note that because ActionScript supports properties, there is no need to define JavaBeans-style setter and getter methods. Note also that we do not need a client-side representation for InventoryManager
.
In addition to serialization, BlazeDS also provides a server-side infrastructure that enables a Flex application to directly invoke methods on a Java object running in a Web container. In this example, we want to invoke InventoryManager
's getCurrentInventory()
method.
On the server, BlazeDS consists of a set of JAR files and a handful of configuration XML documents. You can drop the JARs in a Web application's lib
directory, and the XML files in the WEB-INF
directory. The entire client-server communication, including serialization on the server, takes place via flex.messaging.MessageBrokerServlet
, provided as part of BlazeDS. Thus, you must add this servlet to your application's web.xml
file:
<servlet> <servlet-name>MessageBrokerServlet</servlet-name> <servlet-class>flex.messaging.MessageBrokerServlet</servlet-class> <init-param> <param-name>services.configuration.file</param-name> <param-value>/WEB-INF/flex/services-config.xml</param-value> </init-param> <init-param> <param-name>flex.write.path</param-name> <param-value>/WEB-INF/flex</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet>
The servlet parameters tell the servlet where to find its configuration files. One of those configuration files, inside the flex
directory in this example, configures remote access to a server-side Java object's methods. To register InventoryManager
for remote method invocation, add the following to the BlazeDS remoting configuration file:
<destination id="inventorymanager"> <properties> <source>InventoryManager</source> <scope>application</scope> </properties> </destination>
The above configuration instructs BlazeDS to create an instance of InventoryManager
, and make it available in the application scope. As a result of these configuration options:
InventoryManager
and saves it in the Web application context with the name inventorymanager
.inventorymanager
, the name of the method to invoke, as well as parameters for the method.On the client, we need only to change references from HTTPService
to RemoteObject
:
[Bindable] private var books:ArrayCollection; private function onCreationComplete():void { var inventoryManager:RemoteObject = new RemoteObject(); inventoryManager.destination = "inventorymanager"; inventoryManager.addEventListener(ResultEvent.RESULT, function(event:ResultEvent):void { books = event.result as ArrayCollection; books.filterFunction = filterByAuthor; }); inventoryManager.addEventListener(FaultEvent.FAULT, ... inventoryManager.getCurrentInventory(); }
Because we now expect a list of Book
s from the remote call, we replace the XMLListCollection
with an ArrayCollection
.
The remote object reference represents the server-side InventoryManager
; it is left untyped on the client, as we only need to invoke one method on this object. We refer to the remote object by the name it was given in the server-side configuration (see above).
The remote method invocation in this example does not consume any parameters, and returns a collection of typed Book
ActionScript objects. Not only is Book
typed, but each book's publisher
parameter is serialized into an ActionScript Publisher
object.
Notice that the remote operation is invoked via the Java method name, inventoryManger.getCurrentInventory()
. Flex packages the remote invocation into a message, with the remote object and method names as properties of the message.
Apart from changing the books collection and the remote data invocation, everything else in the Flex client remains the same, including the filter function and the datagrid. But in this version, the Flex complier will catch typing errors during development, as Book
and Publisher
are now statically typed objects.
The examples in this article illustrated Flex's ability to incrementally add rich-client features to a Web application. That capability makes Flex a great tool for agile development. By replacing parts of an HTML page with embedded Flex components, a Flex client can arrange its own interaction with server-side data sources, including invoking remote Java objects of an enterprise Java Web application. A forthcoming article in this series will extend these concepts to another JVM-based language, illustrating Flex's integration with server-side Scala objects.
[1] SWFObject library
http://code.google.com/p/swfobject/
[2] Flash Player
http://www.adobe.com/products/flashplayer/
[3] SWF. Wikipedia. Accessed on April 10, 2009.
http://en.wikipedia.org/wiki/SWF
[4] Representation State Transfer (REST). Wikipedia. Accessed on April 21, 2009
http://en.wikipedia.org/wiki/Representational_State_Transfer
[5] JSON. JavaScript Object Notation
http://www.json.org
[6] Adobe Corporation. ActionScript 3 Language Specification 2006.
http://livedocs.adobe.com/specs/actionscript/3/wwhelp/wwhimpl/js/html/wwhelp.htm
[7] ECMA. Standard ECMA 357: EcmaScript for XML(E4X) Specification Second Edition, 2005.
http://www.ecma-international.org/publications/standards/Ecma-357.htm
[8] World Wide Web Consortium. XQuery 1.0: An XML Query Language W3C Recommendation, 2007.
http://www.w3.org/TR/xquery/
[9] World Wide Web Consortium. XML Path Language (XPath) 2.0 W3C Recommendation, 2007.
http://www.w3.org/TR/xpath20/
[10] BlazeDS
http://www.adobe.com/products/flex/
[11] Action Message Format (AMF) 3. Wikipedia. Accessed on April 22, 2009.
http://en.wikipedia.org/wiki/Action_Message_Format
Adobe's Flex 3 SDK
http://www.adobe.com/cfusion/entitlement/index.cfm?e=flex3sdk&sdid=EUTYX
Flex Builder 3
http://www.adobe.com/products/flex/features/flex_builder/
Have an opinion? Readers have already posted 1 comment about this article. Why not add yours?
Frank Sommers is Editor-in-Chief of Artima.
Artima provides consulting and training services to help you make the most of Scala, reactive
and functional programming, enterprise systems, big data, and testing.