RIAs Done Right: Grails, Flex, and EXT GWT

4,533 views

Published on

Your users want a more advanced user interface. You know that your system needs a service-oriented architecture. You're in luck! These two things actually go hand in hand. Not only can you get the best of both worlds but modern technologies and tools even make it fun to develop these systems. Find out how to build RESTful back-end systems with Grails. You can easily add a Flex front end, or you can try Ext GWT: a combination of Ext JS's rich widgets -- all on the Java™ platform, thanks to the Google Web Toolkit.

Published in: Technology
0 Comments
7 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
4,533
On SlideShare
0
From Embeds
0
Number of Embeds
173
Actions
Shares
0
Downloads
271
Comments
0
Likes
7
Embeds 0
No embeds

No notes for slide











































































  • RIAs Done Right: Grails, Flex, and EXT GWT

    1. 1. RIAs Done Right Michael Galpin, eBay @michaelg
    2. 2. Traditional Web Applications
    3. 3. Traditional Web Applications Browser
    4. 4. Traditional Web Applications Request Browser
    5. 5. Traditional Web Applications Request Ser ver Browser
    6. 6. Traditional Web Applications Request HTML Ser ver Browser
    7. 7. Traditional Web Applications Request HTML Ser ver Browser
    8. 8. Welcome to 2009
    9. 9. Welcome to 2009 Browser
    10. 10. Welcome to 2009 Request Browser
    11. 11. Welcome to 2009 Request Web Ser ver Browser
    12. 12. Welcome to 2009 Request Web Ser ver App Browser
    13. 13. Welcome to 2009 Request Web Ser ver App Browser
    14. 14. Welcome to 2009 Request Web Ser ver App Browser
    15. 15. Welcome to 2009 Request Web Ser ver App Request Browser
    16. 16. Welcome to 2009 Request Web Ser ver App Request Browser Data App Server
    17. 17. Why?
    18. 18. Architecture
    19. 19. Architecture client server 1998 pres app “Hello World”
    20. 20. Architecture client server 1998 pres app “Hello World” client server 2003 pres app Ajax
    21. 21. Architecture client server 1998 pres app “Hello World” client server 2003 pres app Ajax client ser ver 2009 pres app RIA
    22. 22. Performance (seriously)
    23. 23. $$$$$$$$$$$$$$$ $$$$$$$$
    24. 24. $$$$$$$$$$$$$$$ $$$$$$$$ Less (Ser ver)Processing Less Ser vers More Money
    25. 25. $$$$$$$$$$$$$$$ $$$$$$$$ Less (Ser ver)Processing More (Client) Processing Less Ser vers More Cores More Money Free! (Thanks Users)
    26. 26. $$$$$$$$$$$$$$$ $$$$$$$$ Less (Ser ver)Processing More (Client) Processing Less Ser vers More Cores More Money Free! (Thanks Users) Less Bandwidth More Money
    27. 27. Mobility
    28. 28. Mobility a.app a.com
    29. 29. Mobility a.app a.app a.com b.com
    30. 30. Mobility a.app a.app a.app a.com b.com facebook.com
    31. 31. Mobility a.app a.app a.app a.com b.com facebook.com a.app a.air
    32. 32. But What About Ajax?
    33. 33. Ajax?
    34. 34. Ajax? Browser
    35. 35. Ajax? Request Browser
    36. 36. Ajax? Request Browser App Server
    37. 37. Ajax? Request Browser App Server
    38. 38. Ajax? Request HTML+JS Browser App Server
    39. 39. Ajax? Request HTML+JS Browser App Server
    40. 40. Ajax? Request HTML+JS XHR Browser App Server
    41. 41. Ajax? Request HTML+JS XHR Browser ??? App Server
    42. 42. Ajax? Request HTML+JS XHR Browser HTML? App Server
    43. 43. Ajax? Request HTML+JS XHR Browser Data? App Server
    44. 44. Ajax? Request HTML+JS XHR Browser Data? App Server
    45. 45. How?
    46. 46. Step 1. Get Serious About SOA
    47. 47. Step 1. Get Serious About SOA
    48. 48. Step 1. Get Serious About SOA X
    49. 49. Step 1. Get Serious About SOA X •REpresentational •State •Transfer •?
    50. 50. Step 2. Get Tools JAX-RS (JSR-311) s ey Jer J Apa che RESTEasy CXF
    51. 51. Domain Model class Story { String link String title String description String tags String category int votesFor int votesAgainst }
    52. 52. Services class SearchService { boolean transactional = false def list() { Story.list() } def listCategory(catName){ Story.findAllWhere(category:catName) } def searchTag(tag){ Story.findAllByTagsIlike(quot;%quot;+tag+quot;%quot;) } }
    53. 53. class StoryService { Services boolean transactional = true def create(story) { story.votesFor = 0 story.votesAgainst = 0 if(!story.save(flush:true) ) { story.errors.each { log.error(it) } } story } def voteFor(storyId){ def story = Story.get(storyId) story.votesFor += 1 if(!story.save(flush:true) ) { story.errors.each { log.error(it) } } story } def voteAgainst(storyId){ // .... } }
    54. 54. class ApiController { Controllers // injected services def searchService def storyService def search = { def results= null def tagResults = null if (params.tag){ tagResults = searchService.searchTag(params.tag) } def catResults = null if (params.category){ catResults = searchService.listCategory(params.category) } if (params.tag && params.category){ def tagMap = [:] tagResults.each{ story -> tagMap[story.id] = story } results = catResults.findAll { tagMap[it.id] != null} } else { if (params.category){ results = catResults } else { results = tagResults } } render results as JSON } def digg = { def story = storyService.voteFor(params.id) render story as XML } }
    55. 55. Step 3
    56. 56. Step 3a. Just Pick One
    57. 57. GWT in Action public class DiggApp implements EntryPoint { private HorizontalPanel createSearchForm() { HorizontalPanel panel = new HorizontalPanel(); panel.setTitle(quot;Search for Storiesquot;); Label tagLabel = new Label(quot;Tag:quot;); final TextBox tagBox = new TextBox(); panel.add(tagLabel); panel.add(tagBox); Label catLabel = new Label(quot;Category:quot;); final ListBox catBox = new ListBox(); catBox.addItem(quot;quot;); for (String[] category : CATEGORIES){ catBox.addItem(category[0],category[1]); } panel.add(catLabel); panel.add(catBox); Button searchBtn = new Button(quot;Searchquot;); searchBtn.addClickListener(new ClickListener(){ public void onClick(Widget sender) { search(tagBox.getText(), catBox.getValue(catBox.getSelectedIndex())); } }); panel.add(searchBtn); return panel; } }
    58. 58. Mo’ GWT public class DiggApp implements EntryPoint { private final void search(String tag, String category){ resultsPanel.clear(); Story.search(tag, category, new RequestCallback(){ public void onError(Request request, Throwable exception) { Label label = new Label(quot;Sorry there was an errorquot;); resultsPanel.add(label); } public void onResponseReceived(Request request, Response response) { List<Story> stories = Story.fromJson(response.getText()); //SearchTable grid = new SearchTable(stories); StoryGrid grid = new StoryGrid(stories); resultsPanel.add(grid); } }); } }
    59. 59. public class Story { GWT Models public Story(JSONObject obj){ id = (int) obj.get(quot;idquot;).isNumber().doubleValue(); link = obj.get(quot;linkquot;).isString().stringValue(); title = obj.get(quot;titlequot;).isString().stringValue(); description = obj.get(quot;descriptionquot;).isString().stringValue(); tags = obj.get(quot;tagsquot;).isString().stringValue(); category = obj.get(quot;categoryquot;).isString().stringValue(); votesFor = (int) obj.get(quot;votesForquot;).isNumber().doubleValue(); votesAgainst = (int) obj.get(quot;votesAgainstquot;).isNumber().doubleValue(); } public static void search(String tag, String category, RequestCallback callback){ String url = buildRequest(tag, category); RequestBuilder builder = new RequestBuilder(RequestBuilder.GET, url); try { builder.sendRequest(null, callback); } catch (RequestException e) { callback.onError(null, e); } } public static List<Story> fromJson(String jsonText){ JSONValue val = JSONParser.parse(jsonText); JSONArray array = val.isArray(); List<Story> stories = new ArrayList<Story>(array.size()); for (int i=0;i<array.size();i++){ Story story = new Story(array.get(i).isObject()); stories.add(story); } return stories; } }
    60. 60. Plain ‘Ol GWT Table public class SearchTable extends FlexTable { private List<Story> stories; public SearchTable(List<Story> stories) { super(); this.stories = stories; this.buildTable(); } private void buildTable(){ this.setBorderWidth(2); this.setText(0, 0, quot;storyquot;); this.setText(0, 1, quot;categoryquot;); this.setText(0, 2, quot;descriptionquot;); if (stories.size() == 0){ showMessage(quot;Sorry there were no resultsquot;); } else { for (int i=0;i<stories.size();i++){ Story story = stories.get(i); setWidget(i+1, 0, story.getTitleLink()); setText(i+1, 1, story.getCategory()); setText(i+1, 2, story.getDescription()); } } } private void showMessage(String msg){ setText(1,0, msg); getFlexCellFormatter().setColSpan(1, 0, 3); } }
    61. 61. Pimp my GWT public class StoryGrid extends LayoutContainer { public StoryGrid(List<Story> stories){ this.setLayout(new FlowLayout(10)); this.setSize(750, 300); ListStore<BaseModelData> store = this.buildDataModel(stories); Grid<BaseModelData> grid = new Grid<BaseModelData>(store, createColumnModel()); grid.setBorders(true); add(grid); } private ColumnModel createColumnModel(){ List<ColumnConfig> configs = new ArrayList<ColumnConfig>(); ColumnConfig column = new ColumnConfig(); column.setId(quot;titleLinkquot;); column.setHeader(quot;Storyquot;); column.setWidth(200); configs.add(column); //... return new ColumnModel(configs); } private ListStore<BaseModelData> buildDataModel(List<Story> stories){ ListStore<BaseModelData> data = new ListStore<BaseModelData>(); for (Story story : stories){ BaseModelData model = new BaseModelData(story.properties()); data.add(model); } return data; } }
    62. 62. I Want Your Flex
    63. 63. MmmmXML <ctrl:DiggController xmlns:mx=quot;http://www.adobe.com/2006/mxmlquot; layout=quot;verticalquot; xmlns:works=quot;components.*quot; xmlns:ctrl=quot;controllers.*quot;> <mx:Script> <![CDATA[ import org.developerworks.digg.Story; private function digg():void { this.diggStory(results.selectedItem as Story); } private function bury():void { this.buryStory(results.selectedItem as Story); } ]]> </mx:Script> <ctrl:states> <mx:State name=quot;SubmitStoryquot;> <mx:AddChild relativeTo=quot;{buttons}quot; position=quot;afterquot;> <works:StoryEditor successHandler=quot;{this.submissionHandler}quot;/> </mx:AddChild> </mx:State> </ctrl:states> <mx:DataGrid id=quot;resultsquot; dataProvider=quot;{stories}quot; doubleClickEnabled=quot;truequot; doubleClick=quot;openStory(results.selectedItem as Story)quot;/> <mx:HBox id=quot;buttonsquot;> <mx:Button label=quot;Digg the Story!quot; click=quot;digg()quot;/> <mx:Button label=quot;Bury the Story!quot; click=quot;bury()quot;/> <mx:Button label=quot;{this.subBtnLabel}quot; click=quot;toggleSubmitStory()quot;/> </mx:HBox> </ctrl:DiggController>
    64. 64. Components <mx:VBox xmlns:mx=quot;http://www.adobe.com/2006/mxmlquot; width=quot;100%quot; height=quot;100%quot;> <mx:Script> <![CDATA[ [Bindable] private var story:Story = new Story(); public var successHandler:Function; private function submitStory():void { story.addEventListener(DiggEvent.ON_STORY_SUBMIT_SUCCESS, successHandler); story.addEventListener(DiggEvent.ON_STORY_SUBMIT_FAILURE, errorHandler); story.save(); // reset story = new Story(); } ]]> </mx:Script> <mx:Form> <mx:FormHeading label=quot;Submit a New Storyquot;/> <mx:FormItem label=quot;What's the URL?quot; required=quot;truequot;> <mx:TextInput id=quot;linkBoxquot; toolTip=quot;Keep it Short and Sweetquot; text=quot;{story.link}quot;/> </mx:FormItem> <mx:FormItem label=quot;Give it a Titlequot; required=quot;truequot;> <mx:TextInput id=quot;titleBoxquot; text=quot;{story.title}quot;/> </mx:FormItem> <mx:FormItem label=quot;Pick a Categoryquot; required=quot;truequot;> <mx:ComboBox dataProvider=quot;{Story.CATEGORIES}quot; id=quot;categoryBoxquot; selectedIndex=quot;0quot;/> </mx:FormItem> <mx:FormItem label=quot;Give a Short Descriptionquot;> <mx:TextArea height=quot;60quot; width=quot;320quot; id=quot;descripBoxquot; text=quot;{story.description}quot;/> </mx:FormItem> <mx:FormItem label=quot;Tag Itquot;> <mx:TextInput id=quot;tagBoxquot; text=quot;{story.tags}quot;/> </mx:FormItem> <mx:Button label=quot;Submit It!quot; click=quot;submitStory()quot;/> </mx:Form> <mx:Binding source=quot;linkBox.textquot; destination=quot;story.linkquot;/> <mx:Binding source=quot;titleBox.textquot; destination=quot;story.titlequot;/> <mx:Binding source=quot;categoryBox.selectedItem.dataquot; destination=quot;story.categoryquot;/> <mx:Binding source=quot;descripBox.textquot; destination=quot;story.descriptionquot;/> <mx:Binding source=quot;tagBox.textquot; destination=quot;story.tagsquot;/>
    65. 65. Flex Models public class Story extends EventDispatcher { public function Story(data:XML=null) { if (data) { id = data.@id; title = data.title; link = data.link; category = data.category; description = data.description; tags = data.tags; votesFor = Number(data.votesFor); votesAgainst = Number(data.votesAgainst); } } public function save():void { var req:URLRequest = new URLRequest(SUBMIT_URL); req.method = URLRequestMethod.POST; var params:URLVariables = new URLVariables(); params.title = title; params.link = link; params.category = category; params.description = description; params.tags = tags; req.data = params; submitStoryLoader = new URLLoader(req); submitStoryLoader.addEventListener(Event.COMPLETE, submitSuccessHandler); submitStoryLoader.addEventListener(IOErrorEvent.IO_ERROR, submitErrorHandler); submitStoryLoader.load(req); }
    66. 66. Now Is The Time Questions Protests Cries for Help Confessions Donations

    ×