One day Chris Richardson, in need of a rich UI and deeply frustrated with Javascript and CSS, sat on his couch and downloaded FlexBuilder. This is what he found out.
1. Flex for Java developers:
My quest for pain-free UI
development
Chris Richardson
Author of POJOs in Action
www.chrisrichardson.net
chris@chrisrichardson.net
@crichardson
Cinco de Mayo 2009
2. About Chris
Grew up in England and live in Oakland
•
Over 20+ years of software
•
development experience including 12
years of Java
Started Java architecture consulting
•
company and sold it to BEA
Speaker at JavaOne, SpringOne, etc.
•
Java Champion
•
Run a consulting and training company
•
that helps organizations reduce
g
development costs and i
d l d increase
effectiveness
cloudtools.org
www.cloudfoundry.com
Slide 2
Copyright (c) 2009 Chris Richardson. All rights reserved.
3. Agenda
g
The joy and pain of UI
development
Overview of Flex
Developing Flex Applications
Pushing data to the client
g
Building and testing Flex applications
Slide 3
Copyright (c) 2009 Chris Richardson. All rights reserved.
4. Arms race: Frameworks vs. UI complexity
p y
1998
1998- 2000
2000- 2002
2002- 2005
2005-
2007 2008
1999 2002 2005 2006
Really Really More More More
simply Simple Complex Complex complex Rich UIs
pages Pages Pages Pages pages
Home Spring
Spring
grown MVC
Struts Struts Spring
MVC/Web
frame 1.0 1.0 MVC WebFlow
Flow
work Dojo
/☺ ☺
Slide 4
Copyright (c) 2009 Chris Richardson. All rights reserved.
6. Cloud Foundry UI
y
Single page application
Dojo toolkit
Uses DWR to push events to browser
p
End result is quite nice
But getting there was painful
Slide 6
Copyright (c) 2009 Chris Richardson. All rights reserved.
7. Why is building RIAs so painful?
y g p
Open-source JavaScript projects
Variable quality
q y
Some are poorly documented
Built on a shaky foundation of web technologies
JavaScript
p
Dynamic ⇒ limited IDE support
Prototype-based ⇒ simulated classes
No packages ⇒ build your own
…
CSS layout
Difficult to learn
Relies on “hacks” to accomplish basic layout tasks
Lack of portability across browsers
Easier to use tables?
Numerous b
N browser incompatibilities
i tibiliti
Slide 7
Copyright (c) 2009 Chris Richardson. All rights reserved.
8. Today's web = multi-layer hack
y y
Was – a straightforward Hacks to
enable offline
hypertext browsing system
applications
Now – an application …
delivery platform Fake class
system
⇒Time for a Javascript
change Hypertext
Slide 8
Copyright (c) 2009 Chris Richardson. All rights reserved.
9. GWT is an option
p
Treats JavaScript as the runtime
environment
Develop and debug in Java
quot;Swing-stylequot; programming model
But my social network liked Flex …
Slide 9
Copyright (c) 2009 Chris Richardson. All rights reserved.
10. So I downloaded Flex Builder
and wrote some Flex code…
d l d
Slide 10
Copyright (c) 2009 Chris Richardson. All rights reserved.
11. Agenda
g
The joy and pain of UI development
Overview of Flex
Developing Flex Applications
pg pp
Pushing data to the client
Building and testing Flex applications
Slide 11
Copyright (c) 2009 Chris Richardson. All rights reserved.
12. What is Flex?
Open-source framework for building
Rich Internet Applications
Current version is Flex 3 (Flex 4 in 4Q09)
Flex apps run on the ubiquitous Flash
pp q
player
In the browser
On the desktop with Adobe AIR
Excellent documentation
Develop applications in
MXML – declaratively define UI
ActionScript 3 – handle events, invoke
backend services, dynamically construct UI
,y y
Slide 12
Copyright (c) 2009 Chris Richardson. All rights reserved.
13. ActionScript 3 – the scripting
language for the Flash p y
gg player
Class-based, object-oriented language
Compile-time type checking
Packages
Dynamic vs. sealed classes
Dynamic – add properties/methods at
runtime
ti
Method closures
High
Hi h performance AVM2 with JIT
f ith
compiler
Dialect
Di le t of ECMAS ipt (like J
ECMAScript JavaScript)
S ipt)
Slide 13
Copyright (c) 2009 Chris Richardson. All rights reserved.
14. ActionScript XML support (
p pp (E4X)
)
var x : XML = <createAlbumRequest>
<title>{title}</title> var albums : XML = <albums>
<album>
<creationDate>
<id>id1</id>
{creationDate.time} <title>t1</title>
/
</creationDate> <thumbnail>xyz<thumbnail>
<notes>{notes}</notes> </album>
<album>
</createAlbumRequest> <id>id2</id>
<title>t2</title>
<thumbnail>abc</thumbnail>
var title : String = x.title; </album>
var notes : String = x.notes; …..
</albums>
var notes : XMLList = albums.album.notes
var albums2: XMLList = ablums.album.(title = “t1”)
Slide 14
Copyright (c) 2009 Chris Richardson. All rights reserved.
15. Comprehensive component library
p p y
Components
have properties and methods
generate events
Visual components
Controls: Button, TextField, …
Container: TabContainer, Form, Box, …
Can be styled with CSS
Non-Visual
Data access components, e.g. HTTP, Web
Services
Validators
Formatters
Slide 15
Copyright (c) 2009 Chris Richardson. All rights reserved.
16. Declaratively define UI in MXML
y
<mx:Application >
<mx:Form>
<mx:FormItem label=quot;Symbolquot; fontWeight=quot;boldquot;>
label= Symbol fontWeight= bold >
<mx:TextInput id=quot;symbolquot; text=quot;AAPLquot; fontWeight=quot;normalquot;/>
</mx:FormItem>
<mx:FormItem label=quot;quot;>
<mx:Button label=quot;Get Quotequot; click=quot;handleClick(event)quot;/>
</mx:FormItem>
/
</mx:Form>
<mx:DataGrid dataProvider=quot;{quotes}quot; width=quot;100%quot;>
<mx:columns>
<mx:DataGridColumn headerText=quot;Symbolquot; dataField=quot;symbolquot;/>
<mx:DataGridColumn headerText=quot;Pricequot; dataField=quot;pricequot;/>
</mx:columns>
/ l
</mx:DataGrid>
</mx:Application>
Equivalent to creating
component tree in
ActionScript
Slide 16
Copyright (c) 2009 Chris Richardson. All rights reserved.
17. Events
Components generate events
Handled by ActionScript code
<mx:Button l b l quot;G t Q t quot; click=quot;handleClick(event)quot;/>
< B tt label=quot;Get Quotequot; li k quot;h dl Cli k( t)quot;/>
private function handleClick(event :Event) : void {
…
}
Slide 17
Copyright (c) 2009 Chris Richardson. All rights reserved.
18. Data Binding
g
Control is updated whenever variable
changes
[Bindable]
private var quotes : Array = [];
<mx:DataGrid dataProvider=quot;{quotes}quot; width=quot;100%quot;>
dataProvider= {quotes} width= 100% >
<mx:columns>
<mx:DataGridColumn headerText=quot;Symbolquot; dataField=quot;symbolquot;/>
<mx:DataGridColumn headerText=quot;Pricequot; dataField=quot;pricequot;/>
</mx:columns>
</mx:DataGrid>
Slide 18
Copyright (c) 2009 Chris Richardson. All rights reserved.
19. Custom components
p
Define
<mx:GridItem >
AlbumThumbnail.mxml
<mx:Script>
subclasses in
<![CDATA[
[Bindable]
MXML or
public var album : XML;
p
public function viewAlbum() : void { ….}
() }
ActionScript
A ti S i t
]]>
</mx:Script>
Use to
<mx:VBox width=quot;100quot; height=quot;125quot;>
modularize
dl i
<mx:Image id quot;i
I id=quot;imgquot; click=quot;viewAlbum()quot;
quot; li k quot; i Alb ()quot;
source=quot;{album.thumbnail}quot; />
</mx:Box>
application
<mx:Text width=quot;100quot; height=quot;25quot;
text=quot;{album.title}quot;/>
3rd party
d
</mx:VBox>
components
</mx:GridItem>
<components:AlbumThumbnail
album=quot;{rp.currentItem}quot;/>
Slide 19
Copyright (c) 2009 Chris Richardson. All rights reserved.
20. Flex security sandbox
y
Uses the Flash Player security model
By default, a Flex application can only
access resources on the site that it
was downloaded from
Cross-domain policy files on remote
server grants access to Flex
applications from other domains
Slide 20
Copyright (c) 2009 Chris Richardson. All rights reserved.
21. Flex Tools from Adobe
Flex SDK
Free, open-source
F
Compilers and command-line debugger
FlexBuilder –
Cheap ($249) Eclipse-based IDE
For Mac and Windows
MXML and ActionScript editors –
completion, renaming and validation
Drag and drop UI builder
Good debugging: breakpoints,
Comprehensive h l
C h i help
Slide 21
Copyright (c) 2009 Chris Richardson. All rights reserved.
22. Flex Stock Quote Demo
Q
Slide 22
Copyright (c) 2009 Chris Richardson. All rights reserved.
23. Flex back-end integration
g
BlazeDS
Open-source project
O j
Allows Flex clients to talk to server-side
Java applications
RPC
Server-push over HTTP
p
LiveCycle Data Services ES
Commercial
Superset of BlazeDS
More scalable
Client/Server d t synchronization
Cli t/S data h i ti
Slide 23
Copyright (c) 2009 Chris Richardson. All rights reserved.
24. Flex Application Architecture
pp
Blaze
DS
SOFEA = Service-Oriented Front-End Architecture
Service Oriented Front End
Slide 24
Copyright (c) 2009 Chris Richardson. All rights reserved.
25. Agenda
g
The joy and pain of UI development
Overview of Flex
Building Flex Applications
The Cairngorm framework
Cloud Photos Example Application
Scenario: displaying albums
S i di l i lb
Scenario: creating a new album
Scenario: copying photos between albums
Pushing data to the client
Building and testing Flex applications
Slide 25
Copyright (c) 2009 Chris Richardson. All rights reserved.
26. Tangled code
g
<?xml version=quot;1.0quot; encoding=quot;utf-8quot;?>
<mx:Application xmlns:mx=quot;http://www.adobe.com/2006/mxmlquot;
layout=quot;absolutequot;
implements=quot;mx.rpc.IResponderquot;
backgroundColor=quot;#ffffffquot;>
<mx:Script>
Data
[Bindable]
private var quote : String;
private function handleClick(event :Event) : void {
var service : HTTPService = new HTTPService();
Data
…
Access
}
Logic
public function result(data:Object):void { …. }
p
public function fault(info:Object):void { }
( j )
]]>
</mx:Script>
<mx:VDividedBox width=quot;414quot; height=quot;217quot;
Presentation logic
….
</mx:VDividedBox>
</mx:Application>
Slide 26
Copyright (c) 2009 Chris Richardson. All rights reserved.
27. Cairngorm framework
g
MVC framework for Flex
Model – data
View – Flex components that display the
model (through binding)
Controller – implements quot;business logicquot;
logicquot;,
i.e. accessing backend services and update
the model
Encourages:
Separation of concerns
Separation of development roles: front-end
and b k
d back-endd
Avoids big ball of mud
An alternative is PureMVC
Slide 27
Copyright (c) 2009 Chris Richardson. All rights reserved.
28. Cairngorm classes and roles
g
Views
Display the model The code has a cookie-
ModelLocator cutter feel to it but I
Provides access to the model
like the structure
“Business” Events
Generated by Views
Front Controller
Routes events to Commands
Commands
Handle events
Contain quot;business logicquot;/data access logic
g/ g
Invoke delegates
Delegates
Proxy for remote services
Contract between front-end and back-end team
Calls b k t
C ll back to command d
ServiceLocator
Centralized registry of (supposedly) all data access components
Used by delegates
Slide 28
Copyright (c) 2009 Chris Richardson. All rights reserved.
29. Cairngorm flow
g
Slide 29
Copyright (c) 2009 Chris Richardson. All rights reserved.
30. Agenda
g
The joy and pain of UI development
Overview of Flex
Building Flex Applications
The Cairngorm framework
Cloud Photos Example Application
Scenario: displaying albums
S i di l i lb
Scenario: creating a new album
Scenario: copying photos between albums
Pushing data to the client
Building and testing Flex applications
Slide 30
Copyright (c) 2009 Chris Richardson. All rights reserved.
31. Cloud Photos Application
pp
Tomcat
RESTful web services
Flex Client
Scala/Spring MVC
/p g
Events
Java/Spring/JMS
Manage your
photos online
Upload and
p
view photos Simple DB
Organize S3
photos into Amazon Web Services
albums
…
Slide 31
Copyright (c) 2009 Chris Richardson. All rights reserved.
32. Cloud Photos – screenshots
Slide 32
Copyright (c) 2009 Chris Richardson. All rights reserved.
33. Cloud Photo – web services
http://localhost:8080/webapp/api/album
<albums>
<album>
<id>e778769a-8432-46ca-b0f1-5c92f33a8710</id>
<title>Pictures of kids</title>
<thumbnail>https://s3.amazonaws.com/…</thumbnail>
</album>
/
<album>
<id>1ff7528a-65aa-4300-a5bb-c5b7e6eba985</id>
<title>Some birds</title>
<thumbnail>https://s3.amazonaws.com…</thumbnail>
p // /
</album>
…..
</albums>
Slide 33
Copyright (c) 2009 Chris Richardson. All rights reserved.
35. Model – a Singleton
g
package net.chrisrichardson.cloudphotos.ui.model {
import com.adobe.cairngorm.model.IModelLocator;
Metadata tag enables binding for all
public properties
[Bindable]
public class CloudPhotosModelLocator implements IModelLocator {
private static var modelLocator:CloudPhotosModelLocator;
public var viewState : String = quot;displayAlbumsquot;;
public var albums : XMLList;
public var currentAlbum : XML
bli tAlb XML;
public static function getInstance():CloudPhotosModelLocator{
if (modelLocator == null) { Singleton
modelLocator = new CloudPhotosModelLocator();();
}
return modelLocator;
}
}
}
Slide 35
Copyright (c) 2009 Chris Richardson. All rights reserved.
36. View - Main application
pp
<?xml version=quot;1.0quot; encoding=quot;utf-8quot;?>
<mx:Application layout=quot;verticalquot;
layout vertical
xmlns:ns1=quot;net.chrisrichardson.cloudphotos.ui.components.*quot;
xmlns:control=quot;net.chrisrichardson.cloudphotos.ui.control.*quot;
xmlns:business=quot;net.chrisrichardson.cloudphotos.ui.business.*quot;
width=quot;100%quot; height=quot;100%quot;>
idth quot;100%quot; h i ht quot;100%quot;
<business:Services id=quot;servicesquot; />
bus ess Se ces d se ces /
<control:Controller id=quot;controllerquot; />
<mx:Label text=quot;Cloud Photosquot; fontSize=quot;33quot;/>
<ns1:HomePage width=quot;100%quot; height=quot;100%quot;>
width quot;100%quot; height quot;100%quot;>
</ns1:HomePage>
</mx:Application>
Slide 36
Copyright (c) 2009 Chris Richardson. All rights reserved.
37. View - HomePage
g
<mx:Vbox …>
<mx:Binding destination=quot;currentStatequot;
destination currentState
source=quot;{CloudPhotosModelLocator.getInstance().viewState}quot;/>
<mx:states>
<mx:State name=quot;displayAlbumsquot;>
<mx:SetProperty name=quot;selectedIndexquot; target=quot;{viewStack}quot; value=quot;{0}quot;/>
</mx:State>
<mx:State name=quot;displayAlbumquot;>
<mx:SetProperty name=quot;selectedIndexquot; target=quot;{viewStack}quot; value=quot;{1}quot;/>
</mx:State>
</mx:states>
<mx:TabNavigator id=quot;tabNavigatorquot; …>
id= tabNavigator >
<mx:ViewStack id=quot;viewStackquot; width=quot;100%quot; height=quot;100%quot; label=quot;My Albumsquot; >
<components:AlbumList id=quot;albumListquot; width=quot;100%quot; height=quot;100%“/>
<components:AlbumView id=quot;albumViewquot; width=quot;100%quot; height=quot;100%“/>
</mx:ViewStack>
…
</mx:TabNavigator>
CloudPhotosModelLocator.getInstance().viewState
</mx:VBox>
determines whether we are viewing albums or an
album
Slide 37
Copyright (c) 2009 Chris Richardson. All rights reserved.
38. Agenda
g
The joy and pain of UI development
Overview of Flex
Building Flex Applications
The Cairngorm framework
Cloud Photos Example Application
Scenario: di l i
S i displaying albums
lb
Scenario: creating a new album
Scenario: copying photos between albums
Pushing data to the client
Building and testing Flex applications
Slide 38
Copyright (c) 2009 Chris Richardson. All rights reserved.
39. Display Albums Flow
py
Slide 39
Copyright (c) 2009 Chris Richardson. All rights reserved.
40. View dispatches event
p
<?xml version=quot;1.0quot; encoding=quot;utf-8quot;?>
<mx:Vbox … creationComplete=quot;displayAlbums()quot;>
<mx:Script>
<![CDATA[
private function displayAlbums():void {
var event : DisplayAlbumsEvent = new DisplayAlbumsEvent();
event.dispatch();
}
private function createAlbum() : void {
PopUpManager.centerPopUp(PopUpManager.createPopUp(this, CreateAlbumPopup, true));
}
]]>
</mx:Script>
<mx:HBox width=quot;100%quot; height=quot;10%quot;>
<mx:Button label=quot;Create Newquot; click=quot;createAlbum()quot;/>
</mx:HBox>
<mx:Tile id=quot;albumGridquot; width=quot;100%quot; height=quot;90%quot;>
<mx:Repeater id quot; quot; d t P
R t id=quot;rpquot; dataProvider=quot;{CloudPhotosModelLocator.getInstance().albums}quot;>
id quot;{Cl dPh t M d lL t tI t () lb }quot;
<components:AlbumThumbnail album=quot;{rp.currentItem}quot;/>
</mx:Repeater>
</mx:Tile>
</mx:VBox>
Slide 40
Copyright (c) 2009 Chris Richardson. All rights reserved.
41. Controller executes command
package net.chrisrichardson.cloudphotos.ui.control
{
import com.adobe.cairngorm.control.FrontController;
import net.chrisrichardson.cloudphotos.ui.command.*;
import net.chrisrichardson.cloudphotos.ui.event.*;
public class Controller extends FrontController
{
public function Controller()
{
initializeCommands();
}
public function initializeCommands() : void
{
addCommand( CreateAlbumEvent.CREATE_ALBUM, CreateAlbumCommand);
addCommand( DisplayAlbumsEvent.DISPLAY_ALBUMS, DisplayAlbumsCommand);
addCommand( DisplayAlbumEvent.DISPLAY_ALBUM, DisplayAlbumCommand);
addCommand( CopyPhotoToAlbumEvent.COPY_PHOTO, CopyPhotoToAlbumCommand);
}
}
}
Slide 41
Copyright (c) 2009 Chris Richardson. All rights reserved.
42. Command calls delegate
g
public class DisplayAlbumsCommand implements ICommand, IResponder
{
public function execute( event:CairngormEvent ):void {
var delegate : DisplayAlbumsDelegate = new DisplayAlbumsDelegate(this);
delegate.displayAlbums();
}
public function result( event : Object ):void {
var albums : XMLList = event.result.album;
var model : Cl dPh t M d lL
d l CloudPhotosModelLocator = Cl dPh t M d lL
t CloudPhotosModelLocator.getInstance();
t tI t ()
model.albums = albums
}
p
public function fault( event : Object ) : void {
( j
// handle error
}
}
Slide 42
Copyright (c) 2009 Chris Richardson. All rights reserved.
43. Delegate invokes service
g
public class DisplayAlbumsDelegate
{
private var responder : IResponder;
private var service : HTTPService;
public function DisplayAlbumsDelegate( responder : IResponder ) {
this.service = ServiceLocator.getInstance().getHTTPService( quot;displayAlbumsquot; );
displayAlbums
this.responder = responder;
}
public function displayAlbums() : void {
var call : Obj t = service.send()
ll Object i d()
call.addResponder(responder);
}
}
Slide 43
Copyright (c) 2009 Chris Richardson. All rights reserved.
44. Service definition
<?xml version=quot;1.0quot; encoding=quot;utf-8quot;?>
<cairngorm:ServiceLocator
xmlns:mx=quot;http://www.adobe.com/2006/mxmlquot;
xmlns:cairngorm http://www.adobe.com/2006/cairngorm >
xmlns:cairngorm=quot;http://www adobe com/2006/cairngormquot;>
<mx:HTTPService id=quot;displayAlbumsquot;
url=quot;http://.../api/albumquot;
resultFormat=quot;e4xquot;
ltF t quot;4quot;
useProxy=quot;falsequot;
method=quot;GETquot; >
</mx:HTTPService>
/
</cairngorm:ServiceLocator>
Slide 44
Copyright (c) 2009 Chris Richardson. All rights reserved.
45. Command updates model
p
public class DisplayAlbumsCommand implements ICommand, IResponder
{
public function execute( event:CairngormEvent ):void {
var delegate : DisplayAlbumsDelegate = new DisplayAlbumsDelegate(this);
delegate.displayAlbums();
}
public function result( event : Object ):void {
var albums : XMLList = event.result.album;
var model : Cl dPh t M d lL
d l CloudPhotosModelLocator = Cl dPh t M d lL
t CloudPhotosModelLocator.getInstance();
t tI t ()
model.albums = albums
}
p
public function fault( event : Object ) : void {
( j <albums>
// handle error <album>…</album>
} <album>…</album>
} …
</albums>
Slide 45
Copyright (c) 2009 Chris Richardson. All rights reserved.
46. View displays model
py
<?xml version=quot;1.0quot; encoding=quot;utf-8quot;?>
<mx:Vbox … creationComplete=quot;displayAlbums()quot;>
<mx:Script>
<![CDATA[
private function displayAlbums():void {
var event : DisplayAlbumsEvent = new DisplayAlbumsEvent();
event.dispatch();
}
private function createAlbum() : void {
PopUpManager.centerPopUp(PopUpManager.createPopUp(this, CreateAlbumPopup, true));
}
]]>
</mx:Script>
<mx:HBox width=quot;100%quot; height=quot;10%quot;>
<mx:Button label=quot;Create Newquot; click=quot;createAlbum()quot;/>
</mx:HBox>
<mx:Tile id=quot;albumGridquot; width=quot;100%quot; height=quot;90%quot;>
<mx:Repeater id quot; quot; d t P
< R t id=quot;rpquot; dataProvider=quot;{CloudPhotosModelLocator.getInstance().albums}quot;>
id quot;{Cl dPh t M d lL t tI t () lb }quot;>
<components:AlbumThumbnail album=quot;{rp.currentItem}quot;/>
</mx:Repeater>
</mx:Tile>
</mx:VBox>
Slide 46
Copyright (c) 2009 Chris Richardson. All rights reserved.
47. View component
p
<album>
<mx:GridItem >
<id>e778769a-8432-46ca-b0f1-5c92f33a8710</id>
<mx:Script>
<title>Pictures of kids</title>
/
<thumbnail>https://s3.amazonaws.com/…</thumbnail>
<![CDATA[
</album>
[Bindable]
public var album : XML;
p
public function viewAlbum() : void {
()
new DisplayAlbumEvent(album.id).dispatch();
CloudPhotosModelLocator.getInstance().viewState = quot;displayAlbumquot;;
}
]]>
</mx:Script>
/ S it
<mx:Box width=quot;100quot; height=quot;125quot;>
<mx:Image id=quot;imgquot; click=quot;viewAlbum()quot; source=quot;{album.thumbnail}quot; />
</mx:Box>
<mx:Text width=quot;100quot; height= 25 text= {album.title} />
width= 100 height=quot;25quot; text=quot;{album title}quot;/>
</mx:VBox>
</mx:GridItem>
Slide 47
Copyright (c) 2009 Chris Richardson. All rights reserved.
48. Agenda
g
The joy and pain of UI development
Overview of Flex
Building Flex Applications
The Cairngorm framework
Cloud Photos Example Application
Scenario: displaying albums
S i di l i lb
Scenario: creating a new album
Scenario: copying photos between albums
Pushing data to the client
Building and testing Flex applications
Slide 48
Copyright (c) 2009 Chris Richardson. All rights reserved.
49. Points of interest
Uses a popup window
Uploads files
Command publishes a Cairngorm
p g
event to notify view that upload is
complete
Slide 49
Copyright (c) 2009 Chris Richardson. All rights reserved.
50. Displaying a popup window
p y g ppp
<?xml version=quot;1.0quot; encoding=quot;utf-8quot;?>
<mx:Vbox … creationComplete=quot;displayAlbums()quot;>
<mx:Script>
<![CDATA[
private function displayAlbums():void {
var event : DisplayAlbumsEvent = new DisplayAlbumsEvent();
event.dispatch();
}
private function createAlbum() : void {
PopUpManager.centerPopUp(PopUpManager.createPopUp(this, CreateAlbumPopup, true));
}
]]>
</mx:Script>
<mx:HBox width=quot;100%quot; height=quot;10%quot;>
<mx:Button label=quot;Create Newquot; click=quot;createAlbum()quot;/>
</mx:HBox>
<mx:Tile id=quot;albumGridquot; width=quot;100%quot; height=quot;90%quot;>
<mx:Repeater id quot; quot; d t P
R t id=quot;rpquot; dataProvider=quot;{CloudPhotosModelLocator.getInstance().albums}quot;>
id quot;{Cl dPh t M d lL t tI t () lb }quot;
<components:AlbumThumbnail album=quot;{rp.currentItem}quot;/>
</mx:Repeater>
</mx:Tile>
</mx:VBox>
Slide 50
Copyright (c) 2009 Chris Richardson. All rights reserved.
51. CreateAlbum popup window
ppp
<mx:TitleWindow>
<mx:Form width=quot;100%quot;>
<mx:FormItem label=quot;Album Titlequot;>
<mx:TextInput id quot; lb
T I id=quot;albumTitlequot; change=quot;enableDisableCreateButton()quot;/>
Ti l quot; h quot; bl Di bl C B ()quot;/
</mx:FormItem>
<mx:FormItem label=quot;Album Datequot;>
<mx:DateChooser id=quot;datequot; change=quot;enableDisableCreateButton()quot;/>
</mx:FormItem>
<mx:FormItem label=quot;Notesquot;> <mx:TextArea id=quot;notesquot;/> </mx:FormItem>
<mx:FormItem label=quot;quot;>
<mx:Button label=quot;Select Images...quot; click=quot;selectFiles()quot;/>
</mx:FormItem>
<mx:FormItem label=quot;quot;>
<Form> provides
<mx:Label text=quot;{photosToUpload.length} imagesquot;/>
</mx:FormItem>
/
an easy way to
<mx:FormItem label=quot;Selected Filesquot; width=quot;100%quot;>
layout the form
<mx:DataGrid dataProvider=quot;{photosToUpload}quot; width=quot;100%quot;>
<mx:columns>
fields
<mx:DataGridColumn headerText=quot;Namequot; dataField=quot;namequot;/>
<mx:DataGridColumn headerText=quot;Datequot; dataField=quot;creationDatequot;/>
</mx:columns>
</mx:DataGrid>
</mx:FormItem>
</mx:Form>
<mx:ControlBar>
<mx:Button id=quot;createButtonquot; label=quot;Create Album click= createAlbum(event) enabled= false />
id= createButton label= Create Albumquot; click=quot;createAlbum(event)quot; enabled=quot;falsequot;/>
<mx:Button label=quot;Cancelquot; click=quot;cancelCreateAlbum()quot;/>
</mx:ControlBar>
</mx:TitleWindow>
Slide 51
Copyright (c) 2009 Chris Richardson. All rights reserved.
52. Selecting files to upload
g p
private var myFileReference:FileReferenceList = new FileReferenceList();
[Bindable]
private var photosToUpload : Array;
private function selectFiles():void {
myFileReference.addEventListener(quot;selectquot;, selectHandler);
myFileReference.browse();
}
private function selectHandler(event:Event):void {
photosToUpload = myFileReference.fileList.slice();
enableDisableCreateButton();
}
private function enableDisableCreateButton() : void {
createButton.enabled = titl V lid t
t B tt bl d titleValidator.validate().type ! quot;invalidquot;
lid t () t != quot;i lidquot;
&& dateValidator.validate().type != quot;invalidquot; && filesSupplied()
}
p
private function filesSupplied() : Boolean {
pp ()
return photosToUpload != null && photosToUpload.length > 0
}
Slide 52
Copyright (c) 2009 Chris Richardson. All rights reserved.
53. Displaying progress and
dispatching Cairngorm event
p g g
<?xml version=quot;1.0quot; encoding=quot;utf-8quot;?>
<mx:TitleWindow>
<mx:Script>
<![CDATA[
private var uploadProgressWindow : PhotoUploadProgressWindow;
private function createAlbum(event:Event) : void {
uploadProgressWindow =
PhotoUploadProgressWindow(PopUpManager.createPopUp(this,
PhotoUploadProgressWindow, true));
PopUpManager.centerPopUp(uploadProgressWindow);
var ev : C
CreateAlbumEvent = new C
t Alb E t CreateAlbumEvent()
t Alb E t()
ev.album = new Album(albumTitle.text, notes.text, date.selectedDate)
ev.photosToUpload = photosToUpload
ev.dispatch()
}
</mx:Script>
</mx:TitleWindow>
Slide 53
Copyright (c) 2009 Chris Richardson. All rights reserved.
54. Uploading a file
p g
public class CreateAlbumDelegate
{
var photo : FileReference;
public function notePhotoAdded(photoId : String) : void {
…
photoIndex = photoIndex + 1;
var ur : URLRequest = new URLRequest();
ur.url = Env.getRootUrl() + quot;api/album/quot; + album.id + quot;/photo/quot; +
p
photoId;
;
this.photo.addEventListener(Event.COMPLETE, completeHandler);
this.photo.upload(ur, quot;photoquot;);
}
public function completeHandler(event: Event) : void { … }
}
Slide 54
Copyright (c) 2009 Chris Richardson. All rights reserved.
55. CreateAlbumCommand
package net.chrisrichardson.cloudphotos.ui.command {
public class CreateAlbumCommand implements ICommand, IResponder
{
public function CreateAlbumCommand() {}
public function execute( event:CairngormEvent ):void {
var delegate : CreateAlbumDelegate = new CreateAlbumDelegate(this);
var album : Album = (event as CreateAlbumEvent).album
var photosToUpload : Array = (event as CreateAlbumEvent).photosToUpload
delegate.createAlbum(album, photosToUpload);
}
Long running
public function result( event : Object ):void {
new AlbumCreatedEvent((event as Album).id).dispatch();
- publishes a
}
Cairngorm
public function fault( event : Object ) : void { … }
event when
finished
}
Slide 55
Copyright (c) 2009 Chris Richardson. All rights reserved.
56. CreateAlbumPopup
pp
<?xml version=quot;1.0quot; encoding=quot;utf-8quot;?>
View subscribes to
<mx:TitleWindow creationComplete=quot;creationComplete()quot;>
AlbumCreatedEvent –
<mx:Script>
easier than binding to
<![CDATA[
model
private function creationComplete() : void {
CairngormEventDispatcher.getInstance()
.addEventListener(quot;albumCreatedquot;, albumCreated);
}
public function albumCreated(event : CairngormEvent) : void {
PopUpManager.removePopUp(uploadProgressWindow);
CloudPhotosModelLocator.getInstance().viewState = quot;displayAlbumquot;;
new DisplayAlbumsEvent().dispatch();
new DisplayAlbumEvent((event as AlbumCreatedEvent).albumId).dispatch();
PopUpManager.removePopUp(this);
}
}
]]>
</mx:Script>
/ p
</mx:TitleWindow>
Slide 56
Copyright (c) 2009 Chris Richardson. All rights reserved.
57. Agenda
g
The joy and pain of UI development
Overview of Flex
O i f Fl
Building Flex Applications
The Cairngorm framework
Cloud Photos Example Application
Scenario: displaying albums
pyg
Scenario: creating a new album
Scenario: copying photos between
albums
Pushing data to the client
Building and testing Flex applications
Slide 57
Copyright (c) 2009 Chris Richardson. All rights reserved.
58. Points of interest
Uses drag and drop to copy a photo
to an album
Slide 58
Copyright (c) 2009 Chris Richardson. All rights reserved.
59. Initiating a drag
g g
<mx:VBox width=quot;100quot; height=quot;175quot;>
<mx:Script>
PhotoThumbnail.mxml
<![CDATA[
[Bindable]
var photo : XML;
private function mouseMoveHandler(event:MouseEvent):void {
var dragInitiator:Image=Image(event.currentTarget);
var ds:DragSource = new DragSource();
ds.addData(photo.id, 'photoId');
DragManager.doDrag(dragInitiator, ds, event);
}
…
]]>
</mx:Script>
<mx:Image id=quot;imgquot; width=quot;100%quot; height=quot;100%quot;
mouseMove=quot;mouseMoveHandler(event)quot;
source=quot;{photo.thumbnail}quot;/>
</mx:VBox>
Slide 59
Copyright (c) 2009 Chris Richardson. All rights reserved.
60. Handling a drop
g p
<mx:VBox width=quot;100quot; height=quot;175quot;>
<mx:Script>
AlbumSummaryThumbnail.mxml
y
<![CDATA[
[Bindable]
[Bi d bl ]
public var album : XML ;
private function dragEnterHandler(event:DragEvent):void {
if (event.dragSource.hasFormat('photoId')) {
var dropTarget:Image=Image(event.currentTarget);
DragManager.acceptDragDrop(dropTarget);
}
}
private function dragDropHandler(event:DragEvent):void {
var photoId : Object = event.dragSource.dataForFormat('photoId');
p j g (p );
new CopyPhotoToAlbumEvent(album.id, String(photoId)).dispatch();
}
]]>
</mx:Script>
<mx:Box width=quot;100quot; height=quot;125quot; verticalScrollPolicy=quot;offquot; horizontalScrollPolicy=quot;offquot;>
<mx:Image dragEnter=quot;dragEnterHandler(event);quot; dragDrop=quot;dragDropHandler(event);quot;
source=quot;{album.thumbnail}quot;/>
</mx:Box>
<mx:Text id=quot;titlequot; width=quot;100quot; height=quot;25quot; text=quot;{album.title}quot;/>
</mx:VBox>
Slide 60
Copyright (c) 2009 Chris Richardson. All rights reserved.
61. Agenda
g
The joy and pain of UI development
Overview of Flex
Developing Flex Applications
pg pp
Pushing data to the client
Building and testing Flex applications
Slide 61
Copyright (c) 2009 Chris Richardson. All rights reserved.
62. Why events?
y
Cloud Photos server asynchronously
uploads photos to S3
Client might display photo before it is
available
⇒
Notify client when a photo is available
Client can reload the image
Slide 62
Copyright (c) 2009 Chris Richardson. All rights reserved.
63. BlazeDS
Open-source project
Connects Flex and AIR clients to Java
backend services
Client-side Flex components
Server-side components, E.g. Servlet
RPC services
Proxying for remote (web) services
Invoke server-side Java object
Publish-Subscribe messaging
Supports integration with JMS
Spring BlazeDS project for simplified
development
Slide 63
Copyright (c) 2009 Chris Richardson. All rights reserved.
64. BlazeDS messaging components
gg p
Client Server
receives
messages from JMS Queue/
Consumer Destination Adapter
Topic
using
Channel Endpoint
corresponds to
http://localhost:8080/webapp/messagebroker/amfpolling
Slide 64
Copyright (c) 2009 Chris Richardson. All rights reserved.
65. Channel/Endpoint options
/ p p
HTTP Options:
Simple polling with piggyback
Long polling – message/connection
Streaming – many messages/connection
Formats:
AMF – efficient binary format
AMFX – XML format
Slide 65
Copyright (c) 2009 Chris Richardson. All rights reserved.
66. Example client-side consumer
p
<?xml version=quot;1.0quot; encoding=quot;utf-8quot;?>
<mx:Canvas creationComplete=quot;creationComplete()quot;>
<mx:Script>
<![CDATA[
![CDATA[
public function creationComplete() : void { consumer.subscribe();}
private function messageHandler(event:MessageEvent):void {
for each (var photoThumbnail : PhotoThumbnail in thumbnailContainer.getChildren()) {
photoThumbnail.reloadIfNecessary(event.message.body.toString());
}
}
Server publishes a JMS event
when it has uploaded an image
]]>
</mx:Script>
to S3.
Client subscribes and reloads
<mx:Consumer id=quot;consumerquot;
destination=quot;message-destinationquot;
images if required
message=quot;messageHandler(event)quot; …/>
<mx:Tile id=quot;thumbnailContainerquot; width=quot;100%quot; height=quot;90%quot;>
<mx:Repeater id=quot;rpquot;
id rp
dataProvider=quot;{CloudPhotosModelLocator.getInstance().currentAlbum.photos.photo}quot;>
<components:PhotoThumbnail photo=quot;{rp.currentItem}quot;/>
</mx:Repeater>
</mx:Tile>
</mx:Canvas>
Slide 66
Copyright (c) 2009 Chris Richardson. All rights reserved.
67. Spring beans for messaging
pg gg
<amq:topic id=quot;destinationquot;
physicalName=quot;org.apache.activemq.spring.Test.spring.embeddedquot;/>
<bean id=quot;consumerJmsTemplatequot; class=quot;org.springframework.jms.core.JmsTemplatequot;>
<property name=quot;connectionFactoryquot; ref=quot;jmsFactoryquot;/>
</bean>
<bean id=quot;producerquot;
class=quot;net.chrisrichardson.kickstart.backend.services.SpringProducerquot;>
<property name=quot;templatequot; ref=quot;myJmsTemplatequot;/>
<property name=quot;destinationquot; ref=quot;destinationquot; />
</bean>
public class SpringProducer {
private JmsTemplate template;
private Destination destination;
public void send(String message) {
template.convertAndSend(destination, message);
}
}
Slide 67
Copyright (c) 2009 Chris Richardson. All rights reserved.
68. BlazeDS MessageBrokerServlet
g
<servlet>
<servlet-name>MessageBrokerServlet</servlet-name>
<servlet-class>flex.messaging.MessageBrokerServlet</servlet-class>
l l fl i kS l / l l
<init-param>
<param-name>services.configuration.file</param-name>
<param-value>/WEB-INF/flex/services-config.xml</param-value>
</init-param>
/i i
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
lt i
<servlet-name>MessageBrokerServlet</servlet-name>
<url-pattern>/messagebroker/*</url-pattern>
</servlet-mapping>
Slide 68
Copyright (c) 2009 Chris Richardson. All rights reserved.
69. services-config.xml
g
Shared by client and server
Messaging Service
One or more adapters
One or more destinations
One or more channels
Destinations
Referenced by client
Source/sink of messages
g
Has an adapter, e.g. JMSAdapter
Channels
Used by a Flex component to communicate with the
BlazeDS server
Bl DS
Communicate with server-side endpoints
Endpoints
URLs that
URL th t are mapped t M
d to MessageBroker servlet
Bk lt
Slide 69
Copyright (c) 2009 Chris Richardson. All rights reserved.
70. Channels and endpoints
p
<services-config>
<channel-definition id quot;
h l d fi i i id=quot;my-polling-amfquot;
lli fquot;
class=quot;mx.messaging.channels.AMFChannelquot;>
<endpoint
url=quot;http://localhost:8080/webapp/messagebroker/amfpollingquot;
class=quot;flex.messaging.endpoints.AMFEndpointquot;/>
l quot;fl i di AMFE d i quot;/
<properties>
<polling-enabled>true</polling-enabled>
<polling-interval-seconds>4</polling-interval-seconds>
</properties>
/ ti
</channel-definition>
</services-config>
/ i fi
Slide 70
Copyright (c) 2009 Chris Richardson. All rights reserved.
71. Messaging service
gg
<service id=quot;message-servicequot; class=quot;flex.messaging.services.MessageServicequot;>
<adapters>
<adapter-definition id=quot;actionscriptquot;
class=quot;flex.messaging.services.messaging.adapters.ActionScriptAdapterquot;
default=quot;truequot; />
<adapter-definition id=quot;jmsquot;
class=quot;flex.messaging.services.messaging.adapters.JMSAdapterquot; />
</adapters>
<default-channels>
<channel ref=quot;my-polling-amfquot; />
ref my polling amf
</default-channels>
<destination id=quot;message-destinationquot;>
<properties>
<jms>
j
<destination-jndi-name>topicjndiname</destination-jndi-name>
…
</jms>
</properties>
/p p
<adapter ref=quot;jmsquot; />
</destination>
</service>
Slide 71
Copyright (c) 2009 Chris Richardson. All rights reserved.
72. Agenda
g
The joy and pain of UI development
Overview of Flex
Developing Flex Applications
pg pp
Pushing data to the client
Building and testing Flex
applications
Slide 72
Copyright (c) 2009 Chris Richardson. All rights reserved.
73. Using Flex Mojos
g j
Open source project
Maven Mojos for building and testing
flex applications
http://code.google.com/p/flex-mojos/
Badly documented but they work
y y
Slide 73
Copyright (c) 2009 Chris Richardson. All rights reserved.
74. Building a Flex client p j
g project
<project>
…
<pluginRepositories>
<pluginRepository>
l iR i
<id>pia-repository</id>
<url>http://repository.sonatype.org/content/groups/flexgroup/</url>
</pluginRepository>
</pluginRepositories>
<dependencies>
<dependency>
<groupId>cairngorm</groupId>
“mvn install” builds SWF
<artifactId>cairngorm</artifactId>
<version>2_2_1</version>
<type>swc</type>
</dependency>
/p y
</dependencies>
<build>
<sourceDirectory>src</sourceDirectory>
<plugins>
<plugin>
<groupId>info.flex mojos</groupId>
<groupId>info.flex-mojos</groupId>
<artifactId>flex-compiler-mojo</artifactId>
</plugin>
….
</plugins>
</build>
</project>
Slide 74
Copyright (c) 2009 Chris Richardson. All rights reserved.
75. Adding the SWF to y
g your WAR
<project>
<packaging>war</packaging>
…
<dependencies>
d d i
<dependency>
<groupId>net.chrisrichardson</groupId>
<artifactId>kickstart-webapp</artifactId>
Input = SWF + Existing WAR file
<version>1.0-SNAPSHOT</version>
<type>war</type>
Output = new WAR file containing SWF
</dependency>
<dependency>
<groupId>net.chrisrichardson</groupId>
<artifactId>photoflexui</artifactId>
<version>1.0-SNAPSHOT</version>
<type>swf</type>
yp / yp
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.sonatype.flexmojos</groupId>
<artifactId>flexmojos-maven-plugin</artifactId>
<executions>
<execution>
<goals><goal>copy-flex-resources</goal></goals>
</execution>
</executions>
</plugin>
…
Slide 75
Copyright (c) 2009 Chris Richardson. All rights reserved.
76. Automated testing
g
$$: HP QTP, RIATest
FlexUnit
Focused on unit tests
Record UI tests with FlexMonkey
Encountered a licensing error during
compilation!
Fluint
Supposedly better than FlexUnit
Not supported by Flex Mojos
Slide 76
Copyright (c) 2009 Chris Richardson. All rights reserved.
77. Selenium-Flex
Looks the most familiar/promising
selenium.flexClick()
selenium flexClick()
selenium.flexWaitForElement()
…
Selenium extensions invoke ActionScript
functions via External interface
Include SeleniumFlexAPI.swc in your
application
External interface seems not to work in IE6
Tricky to get working in FireFox
y g g
Launching SeleniumServer via Java API didn’t
work – extensions not loaded
maven-selenium-plugin worked
pg
Slide 77
Copyright (c) 2009 Chris Richardson. All rights reserved.
78. Example Selenium-Flex test
p
public class WebIntegrationTest extends TestCase {
@Override
protected void setUp() throws Exception {
selenium = new DefaultSeleniumFlex(quot;localhostquot;, 4444, browserType,
quot;http://localhost:8080quot;);
selenium.start();
}
public void test() throws Exception {
selenium.open(quot;http://localhost:8080/webapp/photoflexui.htmlquot;);
waitForFlexApplicationToLoad(quot;createAlbumButtonquot;);
selenium.flexWaitForElement(quot;albumThumbnail[1]quot;);
selenium.flexClick(quot;albumThumbnail[1]quot;);
selenium.flexWaitForElementVisible(quot;backToAlbumsButtonquot;);
selenium.flexClick( backToAlbumsButtons );
selenium flexClick(quot;backToAlbumsButtonsquot;);
selenium.flexWaitForElementVisible(quot;createAlbumButtonquot;);
selenium.flexClick(quot;createAlbumButtonquot;);
}
}
Slide 78
Copyright (c) 2009 Chris Richardson. All rights reserved.
79. My next steps
y p
Write some automated tests
Investigate Spring ActionScript
Dependency injection framework
Promotes loose coupling
“Inject stubs for services”
Investigate ExternalInterface
ActionScript JavaScript
Slide 79
Copyright (c) 2009 Chris Richardson. All rights reserved.
80. Summary
Flex HTML/Javascript/CSS
p
Better paradigm (Open-source) testing
tools are more
Easier to develop
mature
Slide 80
Copyright (c) 2009 Chris Richardson. All rights reserved.
81. Final thoughts
g
Download or contribute to Cloud
Tools today :
y
www.cloudtools.org
Checkout Cloud Foundry:
www.cloudfoundry.com
www cloudfoundry com
Buy my book ☺
Send email:
chris@chrisrichardson.net
Visit my website:
www.chrisrichardson.net
Talk to me about consulting and
training
Phone: 510 904 9832
Slide 81
Copyright (c) 2009 Chris Richardson. All rights reserved.
82. Resources
http://www.actionscript.org/
http://www.adobe.com/devnet/actionscript/articl
es/actionscript3_overview.html
http://www.adobe.com/devnet/flex/
http://www.adobe.com/devnet/flash/
http://www.infoq.com/articles/java-flex-blazeds
Intro link:
http://www.adobe.com/devnet/flex/articles/intro
ducing_cairngorm.html
http://dispatchevent.org/roger/as3-e4x-
rundown/ /
http://myflex.org/presentations/ComparingFlexFr
ameworks.pdf
http://code.google.com/p/flexlib/
p // gg /p/ /
Slide 82
Copyright (c) 2009 Chris Richardson. All rights reserved.