Upload
chris-richardson
View
135
Download
3
Tags:
Embed Size (px)
DESCRIPTION
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.
Citation preview
Flex for Java developers: Flex for Java developers: My quest for pain-free UI
development development
Chris RichardsonAuthor of POJOs in [email protected]
@crichardson
Cinco de Mayo 2009
About ChrisAbout 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 BEAcompany and sold it to BEA
• Speaker at JavaOne, SpringOne, etc.• Java Champion• Run a consulting and training company
that helps organizations reduce d l d i
gdevelopment costs and increase effectiveness
cloudtools.org
Copyright (c) 2009 Chris Richardson. All rights reserved. Slide 2
www.cloudfoundry.com
Agendag
The joy and pain of UI developmentOverview of FlexDeveloping Flex ApplicationsPushing data to the clientgBuilding and testing Flex applications
Copyright (c) 2009 Chris Richardson. All rights reserved. Slide 3
Arms race: Frameworks vs. UI complexityp y
1998- 2000- 2002- 2005-19981999
20002002
20022005
20052006 2007 2008
Really simply pages
Really Simple Pages
More Complex
Pages
More Complex
Pages
More complex pages
Rich UIs
Home grown frame
work
Struts 1.0
Struts 1.0
Spring MVC
Spring MVC/Web
Flow
Spring MVC
WebFlowDojo
/☺ ☺
Copyright (c) 2009 Chris Richardson. All rights reserved. Slide 4
Cloud Foundryy
Slide 5
Cloud Foundry UIy
Single page applicationDojo toolkitUses DWR to push events to browserpEnd result is quite niceBut getting there was painfulBut getting there was painful
Copyright (c) 2009 Chris Richardson. All rights reserved. Slide 6
Why is building RIAs so painful?y g pOpen-source JavaScript projects
Variable qualityq ySome are poorly documentedBuilt on a shaky foundation of web technologies
JavaScriptpDynamic ⇒ limited IDE supportPrototype-based ⇒ simulated classesNo packages ⇒ build your own…
CSS layoutDifficult to learnRelies on “hacks” to accomplish basic layout tasksLack of portability across browsersEasier to use tables?
N b i tibilitiNumerous browser incompatibilities
Copyright (c) 2009 Chris Richardson. All rights reserved. Slide 7
Today's web = multi-layer hacky y
Was – a straightforward Hacks to enable offline applicationshypertext browsing system
Now – an application
applications
…
delivery platformFake class
system
⇒Time for a Javascript
change Hypertext
Copyright (c) 2009 Chris Richardson. All rights reserved. Slide 8
GWT is an optionp
Treats JavaScript as the runtime environmentDevelop and debug in Java"Swing-style" programming model
But my social network liked Flex …
Copyright (c) 2009 Chris Richardson. All rights reserved. Slide 9
So I downloaded Flex Builder
d l dCopyright (c) 2009 Chris Richardson. All rights reserved. Slide 10
and wrote some Flex code…
Agendag
The joy and pain of UI developmentOverview of FlexDeveloping Flex Applicationsp g ppPushing data to the clientBuilding and testing Flex applicationsBuilding and testing Flex applications
Copyright (c) 2009 Chris Richardson. All rights reserved. Slide 11
What is Flex?
Open-source framework for building Rich Internet ApplicationsRich Internet ApplicationsCurrent version is Flex 3 (Flex 4 in 4Q09)
Flex apps run on the ubiquitous Flash pp qplayer
In the browserOn the desktop with Adobe AIROn the desktop with Adobe AIR
Excellent documentationDevelop applications in
MXML – declaratively define UIActionScript 3 – handle events, invoke backend services, dynamically construct UI, y y
Copyright (c) 2009 Chris Richardson. All rights reserved. Slide 12
ActionScript 3 – the scripting language for the Flash playerg g p y
Class-based, object-oriented languageCompile-time type checkingPackagesDynamic vs. sealed classes
Dynamic – add properties/methods at tiruntime
Method closuresHi h f AVM2 ith JIT High performance AVM2 with JIT compilerDi le t of ECMAS ipt (like J S ipt)Dialect of ECMAScript (like JavaScript)
Copyright (c) 2009 Chris Richardson. All rights reserved. Slide 13
ActionScript XML support (E4X)p pp ( )
var x : XML = <createAlbumRequest>var x : XML = <createAlbumRequest><title>{title}</title><creationDate>
{creationDate.time}
var albums : XML = <albums><album><id>id1</id><title>t1</title>
</creationDate><notes>{notes}</notes>
</createAlbumRequest>
/<thumbnail>xyz<thumbnail>
</album><album><id>id2</id><title>t2</title>
var title : String = x.title;var notes : String = x.notes;
<title>t2</title><thumbnail>abc</thumbnail>
</album>…..
</albums>
var notes : XMLList = albums.album.notes
var albums2: XMLList = ablums.album.(title = “t1”)
Copyright (c) 2009 Chris Richardson. All rights reserved. Slide 14
Comprehensive component libraryp p y
Components have properties and methodshave properties and methodsgenerate events
Visual componentsControls: Button, TextField, …Container: TabContainer, Form, Box, …Can be styled with CSSCan be styled with CSS
Non-Visual Data access components, e.g. HTTP, Web ServicesServicesValidatorsFormatters
Copyright (c) 2009 Chris Richardson. All rights reserved. Slide 15
Declaratively define UI in MXMLy<mx:Application >
<mx:Form><mx:FormItem label="Symbol" fontWeight="bold"><mx:FormItem label= Symbol fontWeight= bold >
<mx:TextInput id="symbol" text="AAPL" fontWeight="normal"/></mx:FormItem><mx:FormItem label="">
<mx:Button label="Get Quote" click="handleClick(event)"/></mx:FormItem>/
</mx:Form><mx:DataGrid dataProvider="{quotes}" width="100%">
<mx:columns><mx:DataGridColumn headerText="Symbol" dataField="symbol"/><mx:DataGridColumn headerText="Price" dataField="price"/>/ l</mx:columns>
</mx:DataGrid></mx:Application>
Equivalent to creating component tree in
Copyright (c) 2009 Chris Richardson. All rights reserved. Slide 16
ActionScript
Events
Components generate eventsHandled by ActionScript code
< B tt l b l "G t Q t " li k "h dl Cli k( t)"/><mx:Button label="Get Quote" click="handleClick(event)"/>
private function handleClick(event :Event) : void {…
}
Copyright (c) 2009 Chris Richardson. All rights reserved. Slide 17
Data Bindingg
Control is updated whenever variable Control is updated whenever variable changes
[Bindable]private var quotes : Array = [];
<mx:DataGrid dataProvider="{quotes}" width="100%"><mx:DataGrid dataProvider= {quotes} width= 100% ><mx:columns><mx:DataGridColumn headerText="Symbol" dataField="symbol"/><mx:DataGridColumn headerText="Price" dataField="price"/>
</mx:columns>
Copyright (c) 2009 Chris Richardson. All rights reserved. Slide 18
</mx:DataGrid>
Custom componentsp
Define <mx:GridItem ><mx:Script> AlbumThumbnail.mxml
subclasses in MXML or A ti S i t
<![CDATA[[Bindable]public var album : XML;
public function viewAlbum() : void { ….}
ActionScriptUse to
d l i
p () { }
]]></mx:Script>
<mx:VBox width="100" height="125">I id "i " li k " i Alb ()" modularize
application3 d
<mx:Image id="img" click="viewAlbum()" source="{album.thumbnail}" />
</mx:Box><mx:Text width="100" height="25"
text="{album.title}"/>
3rd party components
</mx:VBox>
</mx:GridItem>
<components:AlbumThumbnail
Copyright (c) 2009 Chris Richardson. All rights reserved. Slide 19
<components:AlbumThumbnailalbum="{rp.currentItem}"/>
Flex security sandboxy
Uses the Flash Player security modelBy default, a Flex application can only access resources on the site that it was downloaded fromCross-domain policy files on remote server grants access to Flex applications from other domains
Copyright (c) 2009 Chris Richardson. All rights reserved. Slide 20
Flex Tools from Adobe
Flex SDKF Free, open-sourceCompilers and command-line debugger
FlexBuilderFlexBuilder –Cheap ($249) Eclipse-based IDEFor Mac and WindowsFor Mac and WindowsMXML and ActionScript editors –completion, renaming and validationDrag and drop UI builderGood debugging: breakpoints, C h i h lComprehensive help
Copyright (c) 2009 Chris Richardson. All rights reserved. Slide 21
Flex Stock Quote DemoQ
Copyright (c) 2009 Chris Richardson. All rights reserved. Slide 22
Flex back-end integrationg
BlazeDSO jOpen-source projectAllows Flex clients to talk to server-side Java applicationsJava applicationsRPCServer-push over HTTPp
LiveCycle Data Services ESCommercialSuperset of BlazeDSMore scalableCli t/S d t h i tiClient/Server data synchronization
Copyright (c) 2009 Chris Richardson. All rights reserved. Slide 23
Flex Application Architecturepp
BlazeDS
SOFEA = Service-Oriented Front-End Architecture
Copyright (c) 2009 Chris Richardson. All rights reserved. Slide 24
SOFEA = Service Oriented Front End Architecture
Agendag
The joy and pain of UI developmentOverview of FlexBuilding Flex Applications
The Cairngorm frameworkCloud Photos Example ApplicationS i di l i lbScenario: displaying albumsScenario: creating a new albumScenario: copying photos between albumsScenario: copying photos between albums
Pushing data to the clientBuilding and testing Flex applicationsBuilding and testing Flex applications
Copyright (c) 2009 Chris Richardson. All rights reserved. Slide 25
Tangled codeg
<?xml version="1.0" encoding="utf-8"?><mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" implements="mx.rpc.IResponder" backgroundColor="#ffffff"><mx:Script>[Bindable] Data[Bindable]private var quote : String;
private function handleClick(event :Event) : void {var service : HTTPService = new HTTPService(); Data
Data
…}
public function result(data:Object):void { …. } public function fault(info:Object):void { }
Data Access Logic
p ( j ) { }]]></mx:Script>
<mx:VDividedBox width="414" height="217" ….
</mx:VDividedBox>Presentation logic
Copyright (c) 2009 Chris Richardson. All rights reserved. Slide 26
</mx:VDividedBox>
</mx:Application>
Cairngorm frameworkg
MVC framework for FlexModel – dataModel – dataView – Flex components that display the model (through binding)Controller implements "business logic" Controller – implements "business logic", i.e. accessing backend services and update the model
Encourages:Encourages:Separation of concerns Separation of development roles: front-end
d b k dand back-endAvoids big ball of mudAn alternative is PureMVCAn alternative is PureMVC
Copyright (c) 2009 Chris Richardson. All rights reserved. Slide 27
Cairngorm classes and rolesgViews
Display the modelModelLocator
The code has a cookie-ModelLocator
Provides access to the model“Business” Events
Generated by ViewsFront Controller
cutter feel to it but I like the structure
Front ControllerRoutes events to Commands
CommandsHandle eventsContain "business logic"/data access logicg / gInvoke delegates
DelegatesProxy for remote servicesContract between front-end and back-end teamC ll b k t dCalls back to command
ServiceLocatorCentralized registry of (supposedly) all data access componentsUsed by delegates
Copyright (c) 2009 Chris Richardson. All rights reserved. Slide 28
Cairngorm flowg
Copyright (c) 2009 Chris Richardson. All rights reserved. Slide 29
Agendag
The joy and pain of UI developmentOverview of FlexBuilding Flex Applications
The Cairngorm frameworkCloud Photos Example ApplicationS i di l i lbScenario: displaying albumsScenario: creating a new albumScenario: copying photos between albumsScenario: copying photos between albums
Pushing data to the clientBuilding and testing Flex applicationsBuilding and testing Flex applications
Copyright (c) 2009 Chris Richardson. All rights reserved. Slide 30
Cloud Photos ApplicationTomcat
pp
Flex ClientScala/Spring MVC
RESTful web services
Events
Manage your
/ p g
Java/Spring/JMS
Manage your photos online
Upload and pview photosOrganize photos into
Simple DB
S3
photos into albums…
Amazon Web Services
Copyright (c) 2009 Chris Richardson. All rights reserved. Slide 31
Cloud Photos – screenshots
Copyright (c) 2009 Chris Richardson. All rights reserved. Slide 32
Cloud Photo – web serviceshttp://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>
Copyright (c) 2009 Chris Richardson. All rights reserved. Slide 33
Application structurepp
Copyright (c) 2009 Chris Richardson. All rights reserved. Slide 34
Model – a Singletong
package net.chrisrichardson.cloudphotos.ui.model {import com.adobe.cairngorm.model.IModelLocator;
[Bindable]public class CloudPhotosModelLocator implements IModelLocator {
private static var modelLocator:CloudPhotosModelLocator;
Metadata tag enables binding for all public properties
private static var modelLocator:CloudPhotosModelLocator;
public var viewState : String = "displayAlbums";
public var albums : XMLList;bli tAlb XMLpublic var currentAlbum : XML;
public static function getInstance():CloudPhotosModelLocator{if (modelLocator == null) {
modelLocator = new CloudPhotosModelLocator();Singleton
();}return modelLocator;
}
}
Copyright (c) 2009 Chris Richardson. All rights reserved. Slide 35
}
}
View - Main applicationpp
<?xml version="1.0" encoding="utf-8"?><mx:Application layout="vertical" <mx:Application layout vertical
xmlns:ns1="net.chrisrichardson.cloudphotos.ui.components.*"xmlns:control="net.chrisrichardson.cloudphotos.ui.control.*" xmlns:business="net.chrisrichardson.cloudphotos.ui.business.*"
idth "100%" h i ht "100%"width="100%" height="100%">
<business:Services id="services" />bus ess Se ces d se ces /<control:Controller id="controller" />
<mx:Label text="Cloud Photos" fontSize="33"/><ns1:HomePage width "100%" height "100%"><ns1:HomePage width="100%" height="100%"></ns1:HomePage>
</mx:Application>
Copyright (c) 2009 Chris Richardson. All rights reserved. Slide 36
View - HomePageg<mx:Vbox …>
<mx:Binding destination="currentState" <mx:Binding destination currentState source="{CloudPhotosModelLocator.getInstance().viewState}"/>
<mx:states><mx:State name="displayAlbums">
<mx:SetProperty name="selectedIndex" target="{viewStack}" value="{0}"/></mx:State></mx:State><mx:State name="displayAlbum">
<mx:SetProperty name="selectedIndex" target="{viewStack}" value="{1}"/></mx:State>
</mx:states>
<mx:TabNavigator id="tabNavigator" ><mx:TabNavigator id= tabNavigator …>
<mx:ViewStack id="viewStack" width="100%" height="100%" label="My Albums" ><components:AlbumList id="albumList" width="100%" height="100%“/><components:AlbumView id="albumView" width="100%" height="100%“/>
</mx:ViewStack>…
</mx:TabNavigator>
</mx:VBox> CloudPhotosModelLocator.getInstance().viewStatedetermines whether we are viewing albums or an album
Copyright (c) 2009 Chris Richardson. All rights reserved. Slide 37
album
Agendag
The joy and pain of UI developmentOverview of FlexBuilding Flex Applications
The Cairngorm frameworkCloud Photos Example ApplicationS i di l i lbScenario: displaying albumsScenario: creating a new albumScenario: copying photos between albumsScenario: copying photos between albums
Pushing data to the clientBuilding and testing Flex applicationsBuilding and testing Flex applications
Copyright (c) 2009 Chris Richardson. All rights reserved. Slide 38
Display Albums Flowp y
Copyright (c) 2009 Chris Richardson. All rights reserved. Slide 39
View dispatches eventp<?xml version="1.0" encoding="utf-8"?><mx:Vbox … creationComplete="displayAlbums()">
<mx:Script><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="100%" height="10%"><mx:Button label="Create New" click="createAlbum()"/>
</mx:HBox>
<mx:Tile id="albumGrid" width="100%" height="90%">R t id " " d t P id "{Cl dPh t M d lL t tI t () lb }"<mx:Repeater id="rp" dataProvider="{CloudPhotosModelLocator.getInstance().albums}">
<components:AlbumThumbnail album="{rp.currentItem}"/></mx:Repeater>
</mx:Tile></mx:VBox>
Copyright (c) 2009 Chris Richardson. All rights reserved. Slide 40
Controller executes commandpackage net.chrisrichardson.cloudphotos.ui.control{import com.adobe.cairngorm.control.FrontController;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() : voidpublic function initializeCommands() : void{
addCommand( CreateAlbumEvent.CREATE_ALBUM, CreateAlbumCommand);addCommand( DisplayAlbumsEvent.DISPLAY_ALBUMS, DisplayAlbumsCommand);addCommand( DisplayAlbumEvent.DISPLAY_ALBUM, DisplayAlbumCommand);addCommand( CopyPhotoToAlbumEvent.COPY_PHOTO, CopyPhotoToAlbumCommand);
}}
}
Copyright (c) 2009 Chris Richardson. All rights reserved. Slide 41
Command calls delegateg
public class DisplayAlbumsCommand implements ICommand, IResponder{
public function execute( event:CairngormEvent ):void {var delegate : DisplayAlbumsDelegate = new DisplayAlbumsDelegate(this);delegate.displayAlbums(); delegate.displayAlbums();
}
public function result( event : Object ):void {var albums : XMLList = event.result.album;
d l Cl dPh t M d lL t Cl dPh t M d lL t tI t ()var model : CloudPhotosModelLocator = CloudPhotosModelLocator.getInstance();model.albums = albums
}
public function fault( event : Object ) : void {p ( j ) {// handle error
}}
Copyright (c) 2009 Chris Richardson. All rights reserved. Slide 42
Delegate invokes servicegpublic class DisplayAlbumsDelegate{private var responder : IResponder;private var service : HTTPService;
public function DisplayAlbumsDelegate( responder : IResponder ) { this.service = ServiceLocator.getInstance().getHTTPService( "displayAlbums" );this.service ServiceLocator.getInstance().getHTTPService( displayAlbums );this.responder = responder;
}
public function displayAlbums() : void {ll Obj t i d()var call : Object = service.send()
call.addResponder(responder);}
}
Copyright (c) 2009 Chris Richardson. All rights reserved. Slide 43
Service definition
<?xml version="1.0" encoding="utf-8"?>
<cairngorm:ServiceLocatorxmlns:mx="http://www.adobe.com/2006/mxml" xmlns:cairngorm="http://www adobe com/2006/cairngorm">xmlns:cairngorm http://www.adobe.com/2006/cairngorm >
<mx:HTTPService id="displayAlbums" url="http://.../api/album"
ltF t " 4 "resultFormat="e4x"useProxy="false" method="GET" >
</mx:HTTPService>/
</cairngorm:ServiceLocator>
Copyright (c) 2009 Chris Richardson. All rights reserved. Slide 44
Command updates modelp
public class DisplayAlbumsCommand implements ICommand, IResponder{
public function execute( event:CairngormEvent ):void {var delegate : DisplayAlbumsDelegate = new DisplayAlbumsDelegate(this);delegate.displayAlbums(); delegate.displayAlbums();
}
public function result( event : Object ):void {var albums : XMLList = event.result.album;
d l Cl dPh t M d lL t Cl dPh t M d lL t tI t ()var model : CloudPhotosModelLocator = CloudPhotosModelLocator.getInstance();model.albums = albums
}
public function fault( event : Object ) : void { <albums>p ( j ) {// handle error
}}
<albums><album>…</album><album>…</album>…
</albums>
Copyright (c) 2009 Chris Richardson. All rights reserved. Slide 45
View displays modelp y<?xml version="1.0" encoding="utf-8"?><mx:Vbox … creationComplete="displayAlbums()">
<mx:Script><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="100%" height="10%"><mx:Button label="Create New" click="createAlbum()"/>
</mx:HBox>
<mx:Tile id="albumGrid" width="100%" height="90%">< R t id " " d t P id "{Cl dPh t M d lL t tI t () lb }"><mx:Repeater id="rp" dataProvider="{CloudPhotosModelLocator.getInstance().albums}"><components:AlbumThumbnail album="{rp.currentItem}"/>
</mx:Repeater></mx:Tile>
</mx:VBox>
Copyright (c) 2009 Chris Richardson. All rights reserved. Slide 46
View componentp<mx:GridItem >
<mx:Script>
<album><id>e778769a-8432-46ca-b0f1-5c92f33a8710</id><title>Pictures of kids</title>
<![CDATA[[Bindable]public var album : XML;
public function viewAlbum() : void {
/<thumbnail>https://s3.amazonaws.com/…</thumbnail>
</album>
p () {new DisplayAlbumEvent(album.id).dispatch();CloudPhotosModelLocator.getInstance().viewState = "displayAlbum";
}
]]>/ S i t</mx:Script>
<mx:Box width="100" height="125"><mx:Image id="img" click="viewAlbum()" source="{album.thumbnail}" />
</mx:Box><mx:Text width="100" height="25" text="{album title}"/><mx:Text width= 100 height= 25 text= {album.title} />
</mx:VBox>
</mx:GridItem>
Copyright (c) 2009 Chris Richardson. All rights reserved. Slide 47
Agendag
The joy and pain of UI developmentOverview of FlexBuilding Flex Applications
The Cairngorm frameworkCloud Photos Example ApplicationS i di l i lbScenario: displaying albumsScenario: creating a new albumScenario: copying photos between albumsScenario: copying photos between albums
Pushing data to the clientBuilding and testing Flex applicationsBuilding and testing Flex applications
Copyright (c) 2009 Chris Richardson. All rights reserved. Slide 48
Points of interest
Uses a popup windowUploads filesCommand publishes a Cairngorm p gevent to notify view that upload is complete
Copyright (c) 2009 Chris Richardson. All rights reserved. Slide 49
Displaying a popup windowp y g p p p<?xml version="1.0" encoding="utf-8"?><mx:Vbox … creationComplete="displayAlbums()">
<mx:Script><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="100%" height="10%"><mx:Button label="Create New" click="createAlbum()"/>
</mx:HBox>
<mx:Tile id="albumGrid" width="100%" height="90%">R t id " " d t P id "{Cl dPh t M d lL t tI t () lb }"<mx:Repeater id="rp" dataProvider="{CloudPhotosModelLocator.getInstance().albums}">
<components:AlbumThumbnail album="{rp.currentItem}"/></mx:Repeater>
</mx:Tile></mx:VBox>
Copyright (c) 2009 Chris Richardson. All rights reserved. Slide 50
CreateAlbum popup windowp p p<mx:TitleWindow><mx:Form width="100%">
<mx:FormItem label="Album Title">T I id " lb Ti l " h " bl Di bl C B ()"/<mx:TextInput id="albumTitle" change="enableDisableCreateButton()"/>
</mx:FormItem><mx:FormItem label="Album Date">
<mx:DateChooser id="date" change="enableDisableCreateButton()"/></mx:FormItem><mx:FormItem label="Notes"> <mx:TextArea id="notes"/> </mx:FormItem><mx:FormItem label="">
<mx:Button label="Select Images..." click="selectFiles()"/></mx:FormItem><mx:FormItem label="">
<mx:Label text="{photosToUpload.length} images"/></mx:FormItem>
<Form> provides an easy way to /
<mx:FormItem label="Selected Files" width="100%"><mx:DataGrid dataProvider="{photosToUpload}" width="100%">
<mx:columns><mx:DataGridColumn headerText="Name" dataField="name"/><mx:DataGridColumn headerText="Date" dataField="creationDate"/>
</mx:columns>
an easy way to layout the form fields
</mx:columns></mx:DataGrid>
</mx:FormItem></mx:Form>
<mx:ControlBar><mx:Button id="createButton" label="Create Album" click="createAlbum(event)" enabled="false"/>
Copyright (c) 2009 Chris Richardson. All rights reserved. Slide 51
<mx:Button id= createButton label= Create Album click= createAlbum(event) enabled= false /><mx:Button label="Cancel" click="cancelCreateAlbum()"/>
</mx:ControlBar>
</mx:TitleWindow>
Selecting files to uploadg pprivate var myFileReference:FileReferenceList = new FileReferenceList();
[Bindable][Bindable]private var photosToUpload : Array;
private function selectFiles():void {myFileReference.addEventListener("select", selectHandler);myFileReference.browse();
}
private function selectHandler(event:Event):void {photosToUpload = myFileReference.fileList.slice();photosToUpload myFileReference.fileList.slice();enableDisableCreateButton();
}
private function enableDisableCreateButton() : void {t B tt bl d titl V lid t lid t () t ! "i lid" createButton.enabled = titleValidator.validate().type != "invalid" && dateValidator.validate().type != "invalid" && filesSupplied()
}
private function filesSupplied() : Boolean {
Copyright (c) 2009 Chris Richardson. All rights reserved. Slide 52
p pp () {return photosToUpload != null && photosToUpload.length > 0
}
Displaying progress and dispatching Cairngorm eventp g g
<?xml version="1.0" encoding="utf-8"?><mx:TitleWindow><mx:Script><![CDATA[private var uploadProgressWindow : PhotoUploadProgressWindow;
private function createAlbum(event:Event) : void {private function createAlbum(event:Event) : void {uploadProgressWindow =
PhotoUploadProgressWindow(PopUpManager.createPopUp(this, PhotoUploadProgressWindow, true));
PopUpManager.centerPopUp(uploadProgressWindow); C t Alb E t C t Alb E t()var ev : CreateAlbumEvent = new CreateAlbumEvent()
ev.album = new Album(albumTitle.text, notes.text, date.selectedDate)ev.photosToUpload = photosToUploadev.dispatch()
}}
</mx:Script>
</mx:TitleWindow>
Copyright (c) 2009 Chris Richardson. All rights reserved. Slide 53
Uploading a filep 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() + "api/album/" + album.id + "/photo/" +
photoId;p ;this.photo.addEventListener(Event.COMPLETE, completeHandler);this.photo.upload(ur, "photo");
}
public function completeHandler(event: Event) : void { … }
}
Copyright (c) 2009 Chris Richardson. All rights reserved. Slide 54
CreateAlbumCommand
package net.chrisrichardson.cloudphotos.ui.command {
public class CreateAlbumCommand implements ICommand, IResponder{
public function CreateAlbumCommand() {}
public function execute( event:CairngormEvent ):void {public function execute( event:CairngormEvent ):void {var delegate : CreateAlbumDelegate = new CreateAlbumDelegate(this);var album : Album = (event as CreateAlbumEvent).albumvar photosToUpload : Array = (event as CreateAlbumEvent).photosToUploaddelegate.createAlbum(album, photosToUpload);
}}
public function result( event : Object ):void { new AlbumCreatedEvent((event as Album).id).dispatch();
}
Long running - publishes a }
public function fault( event : Object ) : void { … }
}
Cairngorm event when finished
Copyright (c) 2009 Chris Richardson. All rights reserved. Slide 55
}
CreateAlbumPopupp p<?xml version="1.0" encoding="utf-8"?><mx:TitleWindow creationComplete="creationComplete()"><mx:Script>
View subscribes to AlbumCreatedEvent –<mx:Script>
<![CDATA[
private function creationComplete() : void {CairngormEventDispatcher.getInstance()
AlbumCreatedEvent –easier than binding to model
.addEventListener("albumCreated", albumCreated);}
public function albumCreated(event : CairngormEvent) : void {PopUpManager.removePopUp(uploadProgressWindow);PopUpManager.removePopUp(uploadProgressWindow);CloudPhotosModelLocator.getInstance().viewState = "displayAlbum";new DisplayAlbumsEvent().dispatch();new DisplayAlbumEvent((event as AlbumCreatedEvent).albumId).dispatch();PopUpManager.removePopUp(this);
}}
}]]>
</mx:Script>
Copyright (c) 2009 Chris Richardson. All rights reserved. Slide 56
/ p
</mx:TitleWindow>
Agendag
The joy and pain of UI developmentO i f FlOverview of FlexBuilding Flex Applications
The Cairngorm frameworkThe Cairngorm frameworkCloud Photos Example ApplicationScenario: displaying albumsp y gScenario: creating a new albumScenario: copying photos between albumsalbums
Pushing data to the clientBuilding and testing Flex applicationsBuilding and testing Flex applications
Copyright (c) 2009 Chris Richardson. All rights reserved. Slide 57
Points of interest
Uses drag and drop to copy a photo to an album
Copyright (c) 2009 Chris Richardson. All rights reserved. Slide 58
Initiating a dragg g<mx:VBox width="100" height="175">
<mx:Script><![CDATA[ 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="img" width="100%" height="100%" mouseMove="mouseMoveHandler(event)"source="{photo.thumbnail}"/>
Copyright (c) 2009 Chris Richardson. All rights reserved. Slide 59
</mx:VBox>
Handling a dropg p<mx:VBox width="100" height="175">
<mx:Script> <![CDATA[
[Bi d bl ]AlbumSummaryThumbnail.mxml
[Bindable]public var album : XML ;
private function dragEnterHandler(event:DragEvent):void {if (event.dragSource.hasFormat('photoId')) {
var dropTarget:Image=Image(event.currentTarget);
y
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="100" height="125" verticalScrollPolicy="off" horizontalScrollPolicy="off"><mx:Image dragEnter="dragEnterHandler(event);" dragDrop="dragDropHandler(event);"
source="{album.thumbnail}"/></mx:Box><mx:Text id="title" width="100" height="25" text="{album.title}"/>
Copyright (c) 2009 Chris Richardson. All rights reserved. Slide 60
</mx:VBox>
Agendag
The joy and pain of UI developmentOverview of FlexDeveloping Flex Applicationsp g ppPushing data to the clientBuilding and testing Flex applicationsBuilding and testing Flex applications
Copyright (c) 2009 Chris Richardson. All rights reserved. Slide 61
Why events?y
Cloud Photos server asynchronously uploads photos to S3Client might display photo before it is available
⇒Notify client when a photo is availableClient can reload the imageClient can reload the image
Copyright (c) 2009 Chris Richardson. All rights reserved. Slide 62
BlazeDS
Open-source project Connects Flex and AIR clients to Java Connects Flex and AIR clients to Java backend services
Client-side Flex componentsServer-side components, E.g. Servlet
RPC servicesProxying for remote (web) servicesProxying for remote (web) servicesInvoke server-side Java object
Publish-Subscribe messagingSupports integration with JMS
Spring BlazeDS project for simplified developmentdevelopment
Copyright (c) 2009 Chris Richardson. All rights reserved. Slide 63
BlazeDS messaging componentsg g p
Client Server
Consumer Destination Adapter JMS Queue/Topic
receives messages from
Channel Endpoint
using
http://localhost:8080/webapp/messagebroker/amfpolling
corresponds to
Copyright (c) 2009 Chris Richardson. All rights reserved. Slide 64
Channel/Endpoint options/ p p
HTTP Options:Simple polling with piggybackLong polling – message/connectionStreaming – many messages/connection
Formats:AMF – efficient binary formatAMFX – XML format
Copyright (c) 2009 Chris Richardson. All rights reserved. Slide 65
Example client-side consumerp<?xml version="1.0" encoding="utf-8"?><mx:Canvas creationComplete="creationComplete()">
<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());
}}
]]></mx:Script>
Server publishes a JMS event when it has uploaded an image to S3.
<mx:Consumer id="consumer" destination="message-destination" message="messageHandler(event)" …/>
<mx:Tile id="thumbnailContainer" width="100%" height="90%"><mx:Repeater id="rp"
Client subscribes and reloads images if required
<mx:Repeater id rp dataProvider="{CloudPhotosModelLocator.getInstance().currentAlbum.photos.photo}">
<components:PhotoThumbnail photo="{rp.currentItem}"/></mx:Repeater>
</mx:Tile>
</mx:Canvas>
Copyright (c) 2009 Chris Richardson. All rights reserved. Slide 66
</mx:Canvas>
Spring beans for messagingp g g g<amq:topic id="destination"
physicalName="org.apache.activemq.spring.Test.spring.embedded"/>
<bean id="consumerJmsTemplate" class="org.springframework.jms.core.JmsTemplate"><property name="connectionFactory" ref="jmsFactory"/>
</bean>
<bean id="producer" class="net.chrisrichardson.kickstart.backend.services.SpringProducer">
<property name="template" ref="myJmsTemplate"/><property name="destination" ref="destination" />
</bean></bean>
public class SpringProducer {private JmsTemplate template;private Destination destination;
public void send(String message) {template.convertAndSend(destination, message);
}
Copyright (c) 2009 Chris Richardson. All rights reserved. Slide 67
}
BlazeDS MessageBrokerServletg
<servlet><servlet-name>MessageBrokerServlet</servlet-name>
l l fl i k S l / l l<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>
/i i</init-param><load-on-startup>1</load-on-startup>
</servlet>
l t i<servlet-mapping><servlet-name>MessageBrokerServlet</servlet-name><url-pattern>/messagebroker/*</url-pattern>
</servlet-mapping>
Copyright (c) 2009 Chris Richardson. All rights reserved. Slide 68
services-config.xmlgShared by client and serverMessaging ServiceMessaging Service
One or more adaptersOne or more destinationsOne or more channels
DestinationsReferenced by clientSource/sink of messagesgHas an adapter, e.g. JMSAdapter
ChannelsUsed by a Flex component to communicate with the Bl DSBlazeDS serverCommunicate with server-side endpoints
EndpointsURL th t d t M B k l tURLs that are mapped to MessageBroker servlet
Copyright (c) 2009 Chris Richardson. All rights reserved. Slide 69
Channels and endpointsp<services-config>
h l d fi i i id " lli f"<channel-definition id="my-polling-amf" class="mx.messaging.channels.AMFChannel">
<endpoint url="http://localhost:8080/webapp/messagebroker/amfpolling" l "fl i d i AMFE d i "/class="flex.messaging.endpoints.AMFEndpoint"/>
<properties><polling-enabled>true</polling-enabled><polling-interval-seconds>4</polling-interval-seconds>
/ ti</properties></channel-definition>
/ i fi</services-config>
Copyright (c) 2009 Chris Richardson. All rights reserved. Slide 70
Messaging serviceg g<service id="message-service" class="flex.messaging.services.MessageService">
<adapters><adapters><adapter-definition id="actionscript"class="flex.messaging.services.messaging.adapters.ActionScriptAdapter"default="true" />
<adapter-definition id="jms"class="flex.messaging.services.messaging.adapters.JMSAdapter" />
</adapters>
<default-channels><channel ref="my-polling-amf" /><channel ref my polling amf />
</default-channels>
<destination id="message-destination"><properties>
j<jms><destination-jndi-name>topicjndiname</destination-jndi-name>…
</jms></properties>
Copyright (c) 2009 Chris Richardson. All rights reserved. Slide 71
/p p<adapter ref="jms" />
</destination></service>
Agendag
The joy and pain of UI developmentOverview of FlexDeveloping Flex Applicationsp g ppPushing data to the clientBuilding and testing Flex Building and testing Flex applications
Copyright (c) 2009 Chris Richardson. All rights reserved. Slide 72
Using Flex Mojosg j
Open source projectMaven Mojos for building and testing flex applicationshttp://code.google.com/p/flex-mojos/Badly documented but they worky y
Copyright (c) 2009 Chris Richardson. All rights reserved. Slide 73
Building a Flex client projectg p j<project>…<pluginRepositories>
l i R i<pluginRepository><id>pia-repository</id><url>http://repository.sonatype.org/content/groups/flexgroup/</url>
</pluginRepository></pluginRepositories><dependencies>
<dependency><groupId>cairngorm</groupId>
<artifactId>cairngorm</artifactId><version>2_2_1</version><type>swc</type></dependency>
“mvn install” builds SWF
/ 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>
Copyright (c) 2009 Chris Richardson. All rights reserved. Slide 74
</project>
Adding the SWF to your WARg y<project><packaging>war</packaging> …d d i<dependencies><dependency>
<groupId>net.chrisrichardson</groupId><artifactId>kickstart-webapp</artifactId><version>1.0-SNAPSHOT</version><type>war</type>
Input = SWF + Existing WAR fileOutput = new WAR file containing SWF</dependency>
<dependency><groupId>net.chrisrichardson</groupId><artifactId>photoflexui</artifactId><version>1.0-SNAPSHOT</version><type>swf</type>
Output = new WAR file containing SWF
yp / yp</dependency>
</dependencies><build><plugins>
<plugin><groupId>org.sonatype.flexmojos</groupId><groupId>org.sonatype.flexmojos</groupId><artifactId>flexmojos-maven-plugin</artifactId><executions>
<execution><goals><goal>copy-flex-resources</goal></goals>
</execution></executions>
Copyright (c) 2009 Chris Richardson. All rights reserved. Slide 75
</executions></plugin>
…
Automated testingg
$$: HP QTP, RIATestFlexUnit
Focused on unit testsRecord UI tests with FlexMonkeyEncountered a licensing error during compilation!
FluintSupposedly better than FlexUnitNot supported by Flex Mojos
Copyright (c) 2009 Chris Richardson. All rights reserved. Slide 76
Selenium-Flex
Looks the most familiar/promisingselenium flexClick()selenium.flexClick()selenium.flexWaitForElement()…
Selenium extensions invoke ActionScriptSelenium extensions invoke ActionScriptfunctions via External interfaceInclude SeleniumFlexAPI.swc in your applicationapplicationExternal interface seems not to work in IE6Tricky to get working in FireFoxy g g
Launching SeleniumServer via Java API didn’t work – extensions not loadedmaven-selenium-plugin workedp g
Copyright (c) 2009 Chris Richardson. All rights reserved. Slide 77
Example Selenium-Flex testppublic class WebIntegrationTest extends TestCase {
@Override@Overrideprotected void setUp() throws Exception {
selenium = new DefaultSeleniumFlex("localhost", 4444, browserType,"http://localhost:8080");
selenium.start();}}
public void test() throws Exception {selenium.open("http://localhost:8080/webapp/photoflexui.html");waitForFlexApplicationToLoad("createAlbumButton");
selenium.flexWaitForElement("albumThumbnail[1]");selenium.flexClick("albumThumbnail[1]");
selenium.flexWaitForElementVisible("backToAlbumsButton");selenium flexClick("backToAlbumsButtons");selenium.flexClick( backToAlbumsButtons );
selenium.flexWaitForElementVisible("createAlbumButton");selenium.flexClick("createAlbumButton");
}
Copyright (c) 2009 Chris Richardson. All rights reserved. Slide 78
}
My next stepsy p
Write some automated testsInvestigate Spring ActionScript
Dependency injection frameworkPromotes loose coupling“Inject stubs for services”
Investigate ExternalInterfaceActionScript JavaScript
Copyright (c) 2009 Chris Richardson. All rights reserved. Slide 79
Summary
Flex HTML/Javascript/CSSBetter paradigmEasier to develop
p(Open-source) testing tools are more maturemature
Copyright (c) 2009 Chris Richardson. All rights reserved. Slide 80
Final thoughtsg
Download or contribute to Cloud Tools today :y
www.cloudtools.org
Checkout Cloud Foundry:
www cloudfoundry comwww.cloudfoundry.com
Buy my book ☺
Send email:
Visit my website:
www.chrisrichardson.net
Talk to me about consulting and training
Phone: 510 904 9832
Copyright (c) 2009 Chris Richardson. All rights reserved. Slide 81
Resourceshttp://www.actionscript.org/http://www.adobe.com/devnet/actionscript/articlhttp://www.adobe.com/devnet/actionscript/articles/actionscript3_overview.htmlhttp://www.adobe.com/devnet/flex/http://www.adobe.com/devnet/flash/http://www.adobe.com/devnet/flash/http://www.infoq.com/articles/java-flex-blazedsIntro link: http://www.adobe.com/devnet/flex/articles/introhttp://www.adobe.com/devnet/flex/articles/introducing_cairngorm.htmlhttp://dispatchevent.org/roger/as3-e4x-rundown//http://myflex.org/presentations/ComparingFlexFrameworks.pdfhttp://code.google.com/p/flexlib/p // g g /p/ /
Copyright (c) 2009 Chris Richardson. All rights reserved. Slide 82