AS3 and anonymous event handlers
Tuesday, December 29, 2009
Event listener models are not new; however, AS3 event listeners are new to me.
I've developed a couple of application in
Java Swing and
Eclipse RCP, and both frameworks rely heavily on the event dispatch/listener model. AS3 being a full fledged Object Oriented programming language also uses it.
This week I continue fixing a few bugs for one the Flash projects I'm working on. Our application aggregates content from Flickr, YouTube, and Twitter and displays it all in one place. Everything coming into the page is piped through REST calls (HTTP GET requests) as XML structures. Making GET requests and consuming XML structures is easy in Flash, because AS3 provides APIs for HTTP connections and XML parsing.
To load an image in our application we need to make 2 Flickr API calls: first, get the sizing information of a particular image; second, load the desired size of the particular image. In both cases, I need to make an HTTP call and then wait for the response to complete, for which I need 2 event listeners registered. Registering events is all fine and all, but I wanted to do something I've been doing for a while in Java--creating anonymous event handlers on the spot.
If you were wondering how to do this, following is a snippet of code to demonstrate the :
function loadImage(FLICKR_KEY:String, PHOTO_ID:String):void {
var sizeURLRequest:URLRequest =
new URLRequest("http://api.flickr.com/"
+ "services/rest/?method=flickr.photos.getSizes"
+ "&api_key="
+ FLICKR_KEY
+ "&photo_id="
+ PHOTO_ID);
var sizeURLLoader : URLLoader = new URLLoader();
sizeURLLoader.addEventListener(Event.COMPLETE, function (event:Event):void {
var sizeXML : XML = XML(event.target.data);
var targetURL : URLRequest = new URLRequest(sizeXML[0].sizes[0].size[4].@source);
imageLoader = new Loader();
imageLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, showImage);
imageLoader.load(targetURL);
});
sizeURLLoader.load(sizeURLRequest);
}
The anonymous listener is the statement
sizeURLLoader.addEventListener(Event.COMPLETE, function (event:Event):void { ... }
. This is the same as implementing an interface as an anonymous class, which is what would be familiar to you if you were a Java developer.
Note that
showImage
could also be implemented as an anonymous listener, but in this case there's too much logic to display the image and therefore is a separate function that looks as follows:
private function showImage($event:Event):void {
// Display image here...
}
RESTful Flex/Flash client
Saturday, December 26, 2009
This week I had to program in AS3. I didn't code from scratch the application I worked on; however, I designed the architecture, so I was familiar with the source and felt confident to jump in to do updates. In the process, I was introduced to the default Flash development environment--CS4.
Coming from a world of Vim, Eclipse, NetBeans, and Visual Studio, I found the IDE lacking in functionality. True enough, I can compile code and export everything into SWFs files. But what about the little extras that make developing software fun? Where's the
vi plugin? Where's the build file? What about code completion?
A couple of Flash developers I know suggested I try Flash Builder, which is based on Eclipse. I downloaded it and coded my first application--a RESTful Flex client. My first application, however, is not a full AS3 app; it's a Flex app coded in a language that is a hybrid of XML and AS3.
The application, which I call TwitterFlex, looks as follows:
The running version is here
TwitterFlex: you click the button and it connects to Twitter's REST API to retrieve the latest 20 public updates.
Let me break down the code in 3 sections--XML stuff, AS3 code, and UI logic--because I think most Flex application will have the same code structure that my toy example has.
XML Stuff: the web service connectionConnecting to web services through HTTP is such a common requirement that the Flex API already includes code to do just that.
Creating an HTTP call that connects to Twitter is done with the following XML code:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application
xmlns:mx="http://www.adobe.com/2006/mxml"
layout="absolute"
viewSourceURL="srcview/index.html">
(1) <mx:HTTPService
(2) id="RESTService"
(3) url="json.jsp"
(4) resultFormat="text"
(5) result="onLoadTweetsResult(event)"
(6) fault="onLoadTweetsFault(event)"
(7) showBusyCursor="true">
(8) </mx:HTTPService>
If you've seen XML files before, the first line should look familiar: with the
mx
directive we're telling whatever will parse this file that we are using Adobe's
http://www.adobe.com/2006/mxml
package.
Line (1) instantiates an
HTTPService
object; in line (2) I give the instance an id of
RESTService
. In line (3) I set the URL value of
json.jsp
(because of cross domain issues, I need to call a local pass through to talk to Twitter--this is a simple JSP and the code is at the end of this entry). Lines (5) and (6) point to the event handlers of the HTTP responses, with (5) handling success and (6) handling failure.
Note that I have a service instantiated, but I haven't connected to it yet. I leave the connection to the service when the user clicks a button (see the UI section below).
AS3 Code: the <mx:Script></mx:String> tagWith the ability to make web service calls, I need to program the event handlers of the HTTP responses and other functions that are needed for user interaction or logic that needs to be performed as part user requests. AS3 code is enclosed in the XML element
<mx:Script>
. The AS3 code in my application looks as follows:
<mx:Script>
<![CDATA[
import mx.controls.dataGridClasses.DataGridColumn;
import mx.messaging.AbstractConsumer;
import mx.rpc.events.ResultEvent;
import mx.rpc.events.FaultEvent;
import mx.collections.ArrayCollection;
import com.adobe.serialization.json.JSON;
[Bindable]
private var tweets:ArrayCollection;
private function loadTweets():void {
RESTService.send();
}
private function
onLoadTweetsResult(event:ResultEvent):void {
var rawJSON:String = String(event.result);
var arrayJSON:Array = JSON.decode(rawJSON) as Array;
tweets = new ArrayCollection(arrayJSON);
}
private function
onLoadTweetsFault(event:FaultEvent):void {
trace(event.fault.toString());
}
private function
getScreenName(tweet:Object,
column:DataGridColumn):String {
return tweet.user.screen_name;
}
private function
getName(tweet:Object,
column:DataGridColumn):String {
return tweet.user.name;
}
]]>
</mx:Script>
The import statements should be obvious. Next, however, is this
[Bindable]
statement just above the
tweets
variable. As per Adobe's documentation, this
metatag is an event listener hook that updates anything using the instance of the data with a message saying that the original copy changed. In short,
[Bindable]
makes
tweets
a global variable.
Next, comes the
loadTweets()
function, which tells the web service I defined earlier to run by executing the
send()
method of the
HTTPService
object.
Handling of the HTTP responses is delegated to the
onLoadTweetsResult()
and
onLoadTweetsFault()
methods. The former, is where Twitter's JSON object is parsed using a JSON library that is available for
download. Before you can use it, first download it and then add it to your Flex Builder's project library (I thought it was a default Adobe package, but it's not--let me save you some time here).
Finally, the last 2 methods,
getScreenName()
and
getName()
, return the value of the fields in the JSON object that I use in the UI components of the app, which I cover next.
The UIThe last portion of the code is the UI of the application. I won't cover the details of every XML tag available, because there are already many examples of this out there. What's more, I only use 5 UI elements: a
VBox
, a
Label
, a
DataGrid
, a
DataGridColumn
, and a
Button
. My UI, in code, looks as follows:
<mx:VBox
width="100%"
height="50%"
paddingBottom="60"
paddingLeft="60"
paddingRight="60"
paddingTop="60">
<mx:Label
text="RESTful Flex/Flash client (jose@josesandoval.com)"
fontSize="24"
fontWeight="bold" />
(1) <mx:DataGrid
dataProvider="{tweets}"
width="100%"
rowCount="12">
(2) <mx:columns>
(3)<mx:DataGridColumn
width="200"
headerText="Screen Name"
labelFunction="getScreenName" />
(4) <mx:DataGridColumn
width="200"
headerText="Name"
labelFunction="getName" />
(5) <mx:DataGridColumn
headerText="Tweet"
dataField="text" />
</mx:columns>
</mx:DataGrid>
<mx:Button
label="Get Tweets"
click="{RESTService.send()}" />
</mx:VBox>
</mx:Application>
The only lines I will cover in detail are numbered. Everything above and below them is obvious.
Line (1) instantiates a
DataGrid
object that is provided in the code space
mx
. The tag's element
dataProvider="{tweets}"
is passing the grid object the
tweets
global variable I instantiated in the AS3 code--you can see how the event dispatching makes sense in the context of the application: if the state of
tweets
changes, every component that is using it has to be notified.
Lines (3) and (4) define columns in the grid. The tag's element
labelFunction
is telling the instance of the particular column that it needs to run the function named in the element's value. For example,
getScreenName
calls the function coded earlier
getScreenName()
and
getName
calls
getName()
. If you look at the functions above, you see that I'm accessing the
user
element of the parsed JSON object.
What about the variable
column:DataGridColumn
in the method's signature? Well, that's another callback registration that it's telling the code that it will be used in an object of type
DataGridColumn
somewhere while executing.
And finally, line (5) doesn't use a function callback. Because the object
tweets
has been brought into the scope of the loop for the
DataGrid
object, I can access a tweet's element directly and therefore I use
dataField="text"
, where
text
is a member of the instance of the global
tweets
.
Final WordsThis XML and AS3 code hybrid is the next evolution of computer languages. Flex is a compilable meta-language, with AS3 scripting capabilities, that lets us take advantage of the ubiquity of the Flash player. Flex apps are Flash apps and will run on any browser that has a Flash player installed.
Even though I'm liking coding in Flash and this new meta-computer language, I wonder why we insist in recreating all the functionality of the web browser in Flash applications? Flash is cool and all, but we can do most of what it does in plain HTML and JavaScript code.
If you want to see the whole listing in one place, Flex gives you the option of attaching the source code to your deployed applications. The source for this app is here
TwitterFlex/srcview/index.html, which you can also access by right-clicking on the application and then selecting "View Source."
json.jspI mentioned earlier that I use a JSP file to serve as a proxy to talk Twitter from the hosting server. This is because you can't make direct calls from a Flash app to Twitter unless you are a registered user. I don't have a developer's API key, and for this example I still want to use the public stream. The JSP file looks as follows:
<%@ page contentType="application/json;
charset=UTF-8" %>
<%@ page import="java.io.BufferedReader,
java.io.IOException,
java.io.InputStreamReader,
java.net.MalformedURLException,
java.net.URL,
java.net.URLConnection" %>
<%
try {
URL twitter =
new URL(
"http://twitter.com/statuses/public_timeline.json");
URLConnection tc = twitter.openConnection();
BufferedReader in =
new BufferedReader(
new InputStreamReader(tc.getInputStream()));
String line;
while ((line = in.readLine()) != null) {
out.println(line);
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
in.close();
}
%>
I open a connection and make an HTTP GET call. I then stream the result back to the caller of the JSP, but I set the data stream to have a MIME type of
application/json
.
Barcelona 2, Estudiantes 1.
Saturday, December 19, 2009
You heard it here first: Barcelona wins one more trophy in an incredible year. And who do you think scored the winning goal? Messi, of course.