Google Web Toolkit: a case study

  • 2,325 views
Uploaded on

"Google Web Toolkit" presents a case-study in GWT v2 development. This is an introductory to intermediate talk that looks at solid practices for developing a rich Web GUI within the context of a …

"Google Web Toolkit" presents a case-study in GWT v2 development. This is an introductory to intermediate talk that looks at solid practices for developing a rich Web GUI within the context of a Spring v3 backend architecture.

In the talk Bryan will present:

* Introduction to GWT (basic demo app)
* Building View
* Building Presenters
* Talking to the Server
* Extending GWT
* DataGrids in GWT

  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Be the first to comment
No Downloads

Views

Total Views
2,325
On Slideshare
0
From Embeds
0
Number of Embeds
0

Actions

Shares
Downloads
39
Comments
0
Likes
1

Embeds 0

No embeds

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
    No notes for slide

Transcript

  • 1. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy The Google Web Toolkit Getting started Using Building Data Grids Views Google Web Toolkit Extending Building GWT Presenters Talking to the Server Bryan Basham Software Alchemy basham47@gmail.com http://www.linkedin.com/in/SoftwareAlchemistBryan Basham – The Google Web Toolkit Slide 1
  • 2. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Getting Started... View Presenter Model Like Java RMI MVP ...but asynchronous RPC Single-Page Model Config Getting started DEMO #1 Using Building Data Grids Views Google Web Toolkit Extending Building GWT Presenters Talking to the ServerBryan Basham – The Google Web Toolkit Slide 2
  • 3. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy GWT Basics ● Single-Page Web Applications ● Rich-client: phat without being fat ● GUI code built in Java but translated into JavaScript at build-time – Java classes represent HTML elements and widgets – Rich client-side domain model using Java POJOs ● Client/server communication via OO-based RPC; a lot like Javas RMIBryan Basham – The Google Web Toolkit Slide 3
  • 4. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Demo: “Hello World”Bryan Basham – The Google Web Toolkit Slide 4
  • 5. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Typical GWT Architecture Desktop Internet Server Database User ........... ........... ........... Client-side Server-side Components Components & DTOs & EntitiesBryan Basham – The Google Web Toolkit Slide 5
  • 6. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Typical GWT Architecture Desktop Internet Server Database User ........... ........... ........... use gen r ac e rate tion sH GWTs serialization and RPC protocol s s all Other TM rc RPC over Ajax/HTTP requests RPC rve Server-side L se /async /impl Components Boundary reads & Entities transfer objects (both directions) DTO GWT ComponentsBryan Basham – The Google Web Toolkit Slide 6
  • 7. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Typical GWT Architecture Desktop Internet Server Database User ........... ........... ........... use gen L SQ r ac e rate tion sH GWTs serialization and RPC protocol delegate to CRUD s s all TM rc RPC over Ajax/HTTP requests RPC Service DAO rve L se /async /impl s» a te re Boundary reads «c transfer objects (both directions) ? DTO Entity GWT Components Spring ComponentsBryan Basham – The Google Web Toolkit Slide 7
  • 8. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Typical GWT Architecture Desktop Internet Server Database User ........... ........... ........... use gen L SQ r ac e rate tion sH GWTs serialization and RPC protocol delegate to CRUD s ll s TM ca RPC over Ajax/HTTP requests RPC Service DAO rv er L se /async /impl s» a te re Boundary reads «c into client-side DTO transforms DTO Transformer Entity GWT Components Spring ComponentsBryan Basham – The Google Web Toolkit Slide 8
  • 9. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Single-Page Web Apps ● All of the GUI is built with GWT Java code, which becomes JavaScript at build-time ● GWT provides a wide variety of panel widgets to hide complexity, such as: – Using a DeckLayoutPanel to manage a deck of sub-panels in a wizard – Using a TabLayoutPanel to manage a complex Domain model ● Use the RootLayoutPanel object to build a GUI that dynamically resizes as the browser resizesBryan Basham – The Google Web Toolkit Slide 9
  • 10. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy SPWA Structure ● The basic SPWA structure of a GWT app: – A single HTML file – One or more CSS files – An “entry point” class – And the web.xml, of course.Bryan Basham – The Google Web Toolkit Slide 10
  • 11. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Example: GWT Root Page <!doctype html> <html> <head> <meta http-equiv="content-type" content="text/html; charset=UTF-8"> <link type="text/css" rel="stylesheet" href="MyApp.css"> <title>Web Application Starter Project</title> <script type="text/javascript" src="myapp/myapp.nocache.js"></script> </head> <body> <!-- OPTIONAL: include this if you want history support --> <iframe src="javascript:" id="__gwt_historyFrame" tabIndex=-1 style="position:absolute;width:0;height:0;border:0"></iframe> <!-- Main View of the app --> <div id=view></div> </body> </html>Bryan Basham – The Google Web Toolkit Slide 11
  • 12. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Example: Entry Point Class public class MyApp implements EntryPoint { /** * This is the entry point method. */ public void onModuleLoad() { MyPresenter page = new MyPresenter(); RootPanel.get("view").add(page.getView()); } } ● A GWT app could have many presenter/views but typically you will want to create a top-level view for the whole SPWA. ● The MyApp is far too simple to need that, but the Alloy demo will demonstrate this architecture.Bryan Basham – The Google Web Toolkit Slide 12
  • 13. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Model-View-Presenter ● Similar to the popular Model-View-Controller pattern, but cleaner separation: ls e r cal RPC serv /async user actions signals events generates HTML View sends data Presenter reads User DTO – User interacts with the View – View interacts with the Presenter – Presenter interacts with the Model – Model provides: ● communication with the Server using RPC ● entity representation using DTOsBryan Basham – The Google Web Toolkit Slide 13
  • 14. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Views ● A View generates the HTML of the page and transfers user actions to the Presenter ● A View is composed of three elements: – The UI-Binder config file (View.ui.xml) declares the layout of the GUIs widgets – The Java file (View.java) provides the code to: ● handle modifying the view when the model changes ● handle user events from the browser – Any static support files, such as style sheets, images and other mediaBryan Basham – The Google Web Toolkit Slide 14
  • 15. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Example: View.ui.xml <!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent"> <ui:UiBinder xmlns:ui=urn:ui:com.google.gwt.uibinder xmlns:g=urn:import:com.google.gwt.user.client.ui> <g:HTMLPanel> <h1>Web Application Starter Project</h1> <table align="center"> <tr> <td colspan="2" style="font-weight:bold;">Please enter your name:</td> </tr> <tr> <td id="nameFieldContainer"> <g:TextBox ui:field="nameField" /> </td> <td id="sendButtonContainer"> <g:Button ui:field="sendButton">Send</g:Button> </td> </tr> <tr> <td colspan="2" style="color:red;" id="errorLabelContainer"> <g:Label ui:field="errorLabel" /> </td> </tr> </table> </g:HTMLPanel> </ui:UiBinder>Bryan Basham – The Google Web Toolkit Slide 15
  • 16. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Example: View.java public class MyView extends Composite { interface MyUiBinder extends UiBinder<HTMLPanel, MyView> { } private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class); @UiField Button sendButton; @UiField TextBox nameField; @UiField Label errorLabel; private final MyPresenter myPresenter; private final GreetingDialogBox dialogBox; public MyView(MyPresenter myPresenter) { this.myPresenter = myPresenter; this.dialogBox = new GreetingDialogBox(/* details skipped */); // createAndBindUi initializes fields initWidget(uiBinder.createAndBindUi(this)); // Focus the cursor on the name field when the app loads nameField.setText("GWT User"); nameField.setFocus(true); nameField.selectAll(); sendButton.addStyleName("sendButton"); } // More code on next slideBryan Basham – The Google Web Toolkit Slide 16
  • 17. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Example: View.java (part 2) public class MyView extends Composite { // More code on previous slide public void displayError(String message) { errorLabel.setText(message); } public void showFailure(final String message) { dialogBox.showFailure(message); } public void showSuccess(final String message) { dialogBox.showSuccess(message); } @UiHandler("nameField") void handleEnterKey(final KeyUpEvent event) { if (event.getNativeKeyCode() == KeyCodes.KEY_ENTER) { sendNameToServer(); } } @UiHandler("sendButton") void handleSendBtn(final ClickEvent event) { sendNameToServer(); } // More code on next slideBryan Basham – The Google Web Toolkit Slide 17
  • 18. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Example: View.java (part 3) public class MyView extends Composite { // More code on previous slide private void sendNameToServer() { // First, we validate the input. errorLabel.setText(""); String name = nameField.getText(); if (!FieldVerifier.isValidName(name)) { errorLabel.setText("Please enter at least four characters"); return; } // Then, we send the input to the server. sendButton.setEnabled(false); dialogBox.reset(name); myPresenter.sendToServer(name); } }Bryan Basham – The Google Web Toolkit Slide 18
  • 19. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Presenters ● A Presenter component converts User actions, via the View, into Server-side commands using an RPC component. ● A Presenter passes data from the Server to the Views, eg) a list of DTOs to a grid. ● Presenter class should be free of View-related aspects (no interaction with widgets): – Makes the business-logic code cleaner – Permits simple unit-testingBryan Basham – The Google Web Toolkit Slide 19
  • 20. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Example: MyPresenter.java public class MyPresenter { /** Create a remote service proxy to talk to the server-side Greeting service. */ private final GreetingServiceAsync greetingRPC = GWT.create(GreetingService.class); private final MyView view; public MyPresenter() { this.view = new MyView(this); } public MyView getView() { return view; } public void sendToServer(final String name) { greetingRPC.sayHello(name, new AsyncCallback<String>() { public void onFailure(Throwable caught) { view.showFailure(SERVER_ERROR); } public void onSuccess(String result) { view.showSuccess(result); } }); } }Bryan Basham – The Google Web Toolkit Slide 20
  • 21. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Data Transfer Objects (DTO) ● DTOs provide a client-side representation of the applications Domain Model – Basically just a JavaBean, with a no-arg ctor and properties with get/set methods – Must implement the IsSerializable interface – Property values are limited to: ● Java primitives and wrapper classes ● Most java.util classes (Date, ArrayList, etc) ● Other GWT-serializable DTO classes ● Why not just use your Entity beans?Bryan Basham – The Google Web Toolkit Slide 21
  • 22. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Remote Procedure Calls (RPC) ● RPC components provide communication with the Server, much like Javas RMI ● GWTs RPC mechanism requires: – The synchronous interface (MyRPC.java) – The implementation class (MyRPCImpl.java) – The asynchronous interface (MyRPCAsync.java) ● The Presenter makes calls to RPC components using the asych interface – This is necessary because Ajax is used, which requires callback functionsBryan Basham – The Google Web Toolkit Slide 22
  • 23. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy RPC (Architecture Diagram) The Async interface is based upon the Sync interface but with an additional Callback<ReturnType> parameter. «interface» «interface» MyRPCAsync MyRPC This class implementation is provided by GWT which contains boiler-plate code to make Ajax requests. server calls GWTs serialization and RPC protocol delegates to Boundary «class» over Ajax/HTTP requests GWT RPC «class» MyRPCAsync_ servlet MyRPCImpl impl GWT also provides a Servlet which: ● Handles the RPC requests ● De-serializes the arguments ● Delegates the RPC call to the appropriate RPC implementation ● Serializes the method return valueBryan Basham – The Google Web Toolkit Slide 23
  • 24. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Example RPC Component ● GreetingService interface: @RemoteServiceRelativePath("greet") public interface GreetingService extends RemoteService { String sayHello(String name) throws IllegalArgumentException; } ● GreetingService implementation class: public class GreetingServiceImpl extends RemoteServiceServlet implements GreetingService { public String sayHello(String input) throws IllegalArgumentException { // Verify that the input is valid. if (!FieldVerifier.isValidName(input)) { throw new IllegalArgumentException( "Name must be at least 4 characters long"); } String serverInfo = getServletContext().getServerInfo(); String userAgent = getThreadLocalRequest().getHeader("User-Agent"); return "Hello, " + input + "!<br><br>I am running " + serverInfo + ".<br><br>It looks like you are using:<br>" + userAgent; } }Bryan Basham – The Google Web Toolkit Slide 24
  • 25. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Example RPC Component (pt 2) ● GreetingServiceAsync interface: public interface GreetingServiceAsync { void sayHello(String input, AsyncCallback<String> callback) throws IllegalArgumentException; } ● web.xml configuration: <servlet> <servlet-name>greetServlet</servlet-name> <servlet-class>com.example.myapp.server.GreetingServiceImpl</servlet-class> </servlet> <servlet-mapping> <servlet-name>greetServlet</servlet-name> <url-pattern>/myapp/greet</url-pattern> </servlet-mapping>Bryan Basham – The Google Web Toolkit Slide 25
  • 26. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy RPC (Interaction Diagram) Boundary «class» GWT RPC GWT RPC «class» MyRPCAsync_ invoker servlet MyRPCImpl impl sayHello(name, callback) payload[sayHello|Fred] sayHello(name) name:String “Fred” name:String “Fred” callback: AsyncCallback<String> return payload[Hello Fred] result:String onSuccess(result) “Hello Fred” result:String “Hello Fred”Bryan Basham – The Google Web Toolkit Slide 26
  • 27. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy GWT Configuration ● A GWT project is organized into Java src and Webapp files in the war directory. ● The MyApp.gwt.xml file is the primary config file.Bryan Basham – The Google Web Toolkit Slide 27
  • 28. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy GWT Configuration <!DOCTYPE module PUBLIC "-//Google Inc.//DTD Google Web Toolkit 2.5.0//EN" "http://google-web-toolkit.googlecode.com/svn/tags/2.5.0/distro-source/core/src/gwt-module.dtd "> <module rename-to=myapp> <!-- Inherit the core Web Toolkit stuff. --> <inherits name=com.google.gwt.user.User/> <!-- Inherit the default GWT style sheet. --> <inherits name=com.google.gwt.user.theme.clean.Clean/> <!-- Specify the app entry point class. --> <entry-point class=com.example.myapp.client.MyApp/> <!-- Specify the paths for translatable code --> <source path=client/> <source path=shared/> </module>Bryan Basham – The Google Web Toolkit Slide 28
  • 29. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy GWT Configuration ● The Java code is roughly organized into three packages: – client: on browser – server: on the server – shared: used on both sidesBryan Basham – The Google Web Toolkit Slide 29
  • 30. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Building Views View Presenter Model Like Java RMI MVP ...but asynchronous RPC Single-Page Model Config Getting Panels Widgets started DEMO #1 Raw DEMO #2 Binding Using Building Data Grids Views UI-Binding Styles / CSS Google Web Toolkit Extending Building GWT Presenters Talking to the ServerBryan Basham – The Google Web Toolkit Slide 30
  • 31. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy DEMO #2 ● Alloy is a Monitor & Control app on a document processing pathway: .... .. ..... .... document1 Datamart1 User .... .. ..... Prospective Warehouse .... document2 Datamart2 .... .. ..... User .... document3 ● Also: search, reports, admin toolsBryan Basham – The Google Web Toolkit Slide 31
  • 32. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy DEMO #2: Types of Pages ● This app contains these types of pages: – Dashboards: shows high-level status & system health – Monitor: view detail status and content flow – Control: modify system properties and other actions – Search: tools to search on document contentBryan Basham – The Google Web Toolkit Slide 32
  • 33. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy DEMO #2: DashboardsHeader Bread- crumb Pages Deck FooterBryan Basham – The Google Web Toolkit Slide 33
  • 34. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy DEMO #2: Monitor pageBryan Basham – The Google Web Toolkit Slide 34
  • 35. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy DEMO #2: Control pageBryan Basham – The Google Web Toolkit Slide 35
  • 36. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy GWT Widgets ● GWT provides basic HTML-based widgets – Button, Radio button, Checkbox, Listbox, Textbox, Textarea, Hyperlink, and more ● And some advanced widgets: – DatePicker, ToggleButton, CellList, MenuBar, Tree, SuggestBox, RichTextArea, and more ● Rich table/grid components ● Click here: Widget GalleryBryan Basham – The Google Web Toolkit Slide 36
  • 37. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Panels ● Panels are complex <div> elements with behavior ● Old-school panels to not handle browser resizing (and put the browser into quirks mode) – HorizontalPanel, VerticalPanel, StackPanel, FlowPanel, DockPanel, PopupPanel, TabPanel ● GWT v2 provides modern, resizable panels – DockLayoutPanel, DeckLayoutPanel, TabLayoutPanel, ScrollPanel, LayoutPanel – ...and DataGridBryan Basham – The Google Web Toolkit Slide 37
  • 38. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Resize-able UI ● Fit the whole UI within the browser window ● Use resize-able layouts from outside inward ● Application.ui.xml: <ui:UiBinder xmlns:ui=urn:ui:com.google.gwt.uibinder xmlns:g=urn:import:com.google.gwt.user.client.ui xmlns:alloy=urn:import:com.tr.cmg.alloy.ui.client.screen> <g:DockLayoutPanel unit="PX"> <g:north size="119"><alloy:Header ui:field="header" /></g:north> <g:center> <g:DockLayoutPanel unit="PX"> <g:north size="29"><alloy:Breadcrumb ui:field="breadCrumb" /></g:north> <g:center><g:DeckLayoutPanel ui:field="pageDeck" /></g:center> </g:DockLayoutPanel> </g:center> <g:south size="30"><alloy:Footer ui:field="footer" /></g:south> </g:DockLayoutPanel> </ui:UiBinder>Bryan Basham – The Google Web Toolkit Slide 38
  • 39. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Alloys Core GUI Architecture ● Application contains many Pages with one View each: Extending this class forces each page «GWT Widget» view to be resize-able. It is the developers responsibility to decide how the pages Resize layout is constructed to support resizing. Composite 1..* «Presenter» 1 «View» «GWT EntryPoint» Abstract Abstract Application pages view Page PageView ... ... «Presenter» «View» Prospective Prospective FeedLoadPage FeedLoadViewBryan Basham – The Google Web Toolkit Slide 39
  • 40. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Raw UI-binding ● GWT provides full access to the pages DOM – One-to-one methods for Element and Node APIs – Plus additional APIs to simplify common tasks, like adding/removing CSS classes ● GWT provides programmatic APIs to compose higher-level widgets (which encode DOM Elements)Bryan Basham – The Google Web Toolkit Slide 40
  • 41. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Raw UI-binding Techniques ● Create strings of content and/or HTML tags and use setInnerHTML method. ● Create Element objects and perform inserts ● Create Widget objects and perform adds ● Use raw binding sparingly; better to use the XML UI-binding config (as seen in other examples)Bryan Basham – The Google Web Toolkit Slide 41
  • 42. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Raw UI-binding ExampleBryan Basham – The Google Web Toolkit Slide 42
  • 43. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Raw UI-binding Example <g:ScrollPanel> <g:HTMLPanel> <!-- Date selection form --> <g:HTMLPanel ui:field="statsGrid"> <table border=1 cellpadding=5px> <thead> <tr> <th>Station</th> <th>Category</th> <th><!-- Date goes here --></th> <th>Last 7 days</th> <th>Last 30 days</th> <th>Last 60 days</th> <th>Last year</th> </tr> </thead> <tbody> <!-- View code fills the body --> </tbody> </table> </g:HTMLPanel> <!-- Reload button form --> </g:HTMLPanel> </g:ScrollPanel>Bryan Basham – The Google Web Toolkit Slide 43
  • 44. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Raw UI-binding Example public class DailyLoadStatsView extends AbstractPageView { // Skipping lots of other View code private void populateStatsGrid(final LoadStatsModelDTO loadStats) { final Document DOM = Document.get(); List<String> categories = loadStats.getCategories(); // get TABLE element Element gridEl = statsGrid.getElement(); Element tableEl = gridEl.getFirstChildElement(); // put date into "day" header Element theadEl = tableEl.getFirstChildElement(); Element theadTR = theadEl.getFirstChildElement(); Element dateTH = theadTR.getFirstChildElement() .getNextSiblingElement().getNextSiblingElement(); Date reportDate = loadStats.getDailyLoadStats().getDateOfStats(); dateTH.setInnerText(DateUtils.asString(reportDate)); // More code on next slide } }Bryan Basham – The Google Web Toolkit Slide 44
  • 45. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Raw UI-binding Example private void populateStatsGrid(final LoadStatsModelDTO loadStats) { // More code on previous slide // build new TBODY tbodyEl = DOM.createTBodyElement(); for (PathwayStation station : PathwayStation.values()) { boolean first = true; boolean odd = true; // Add a row for each category for (String category : categories) { TableRowElement rowEl = DOM.createTRElement(); tbodyEl.appendChild(rowEl); rowEl.addClassName((odd) ? "odd" : "even"); if (first) { // Insert the header on the first category for each station TableCellElement stationTH = DOM.createTHElement(); stationTH.setInnerText(station.name()); stationTH.setRowSpan(categories.size() + 1); rowEl.appendChild(stationTH); first = false; } // Skipping the rest of the code (you get the point) } }Bryan Basham – The Google Web Toolkit Slide 45
  • 46. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy XML UI-binding ● Separation of concerns – View.ui.xml: ● Layout of View widgets ● Styles of widgets within the View – View.java: ● Inject data into the Views widgets ● Handle user events on the Views widgets – Other CSS files: ● Styles of more generic aspects of the UI ● Styles of custom widgetsBryan Basham – The Google Web Toolkit Slide 46
  • 47. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy XML UI-binding Example <!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent"> <ui:UiBinder xmlns:ui=urn:ui:com.google.gwt.uibinder xmlns:g=urn:import:com.google.gwt.user.client.ui xmlns:c=urn:import:com.google.gwt.user.cellview.client> Widget libraries <ui:style> .body { margin: 1em; } .button { margin-top: 0 !important; Page-specific CSS styles margin-bottom: 0 !important; margin-left: 0 !important; margin-right: 0.25em !important; } </ui:style> <g:DockLayoutPanel unit="EM" styleName="{style.body}"> <g:north size="17"> <!-- Search form --> Fixed-width dock region </g:north> <g:center> <c:DataGrid ui:field=settingsGrid /> Resize-able dock region </g:center> </g:DockLayoutPanel> </ui:UiBinder>Bryan Basham – The Google Web Toolkit Slide 47
  • 48. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy XML UI-binding Example <!-- Search form --> <g:north size="17"> <g:DockLayoutPanel unit="EM"> <g:north size="2"> <g:FlowPanel width="100%"> <g:Label stylePrimaryName="alloyLabel">User Group:</g:Label> <g:ListBox ui:field="userGroupList" stylePrimaryName="alloyLabel" width="250" /> </g:FlowPanel> </g:north> <g:center> <g:DockLayoutPanel unit="EM"> <g:north size="2"> <g:FlowPanel width="100%"> <g:Label stylePrimaryName="alloyLabel">Content Set:</g:Label> <g:ListBox ui:field="productList" stylePrimaryName="alloyLabel" width="250" /> </g:FlowPanel> </g:north> <g:center> <g:FlowPanel width="100%"> <g:Label stylePrimaryName="alloyLabel">Collection:</g:Label> <g:ListBox ui:field="collectionList"width="250" multipleSelect="true" /> <!-- Collection selection buttons --> <g:Button ui:field="selectAllBtn">Select All</g:Button> <g:HTML height="1px"> <br /> </g:HTML> <g:Button ui:field="deselectAllBtn">Deselect All</g:Button> </g:FlowPanel> </g:center> </g:DockLayoutPanel> <!-- More View config skipped to fit the slide --> </g:north>Bryan Basham – The Google Web Toolkit Slide 48
  • 49. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy XML UI-binding Example public class FeedLoadSettingsView extends AbstractPageView { interface MyUiBinder extends UiBinder<DockLayoutPanel, FeedLoadSettingsView>{} private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class); /* Search criteria fields */ @UiField ListBox userGroupList; @UiField ListBox productList; @UiField ListBox collectionList; @UiField Button selectAllBtn; @UiField Button deselectAllBtn; /* Action Buttons */ @UiField Button searchBtn; @UiField Button saveBtn; @UiField Button resetBtn; /* Feed/Load Setting grid */ @UiField(provided = true) DataGrid<FeedLoadSettingsDTO> settingsGrid; public FeedLoadSettingsView(final FeedLoadSettingsPage page) { super(page); // pre-binding initialization (“provides” the Grid object) initialize(); // createAndBindUi initializes fields initWidget(uiBinder.createAndBindUi(this)); // post-binding initialization userGroupList.getElement().setId(makeFieldId("userGroupList")); // Skipping more code to fit the slideBryan Basham – The Google Web Toolkit Slide 49
  • 50. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy View Styles ● Use stand-alone CSS files for generic classes of styles; usually for custom widgets ● Use UI-binding styles for Page-specific styles ● Use programmatic controls to change styles at run-time – Do this sparingly – Favor changing classes rather than hard-coding style values – Use GWTs getStyle().setXyzProperty() as a last resortBryan Basham – The Google Web Toolkit Slide 50
  • 51. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Building Presenters View Presenter Model Like Java RMI MVP ...but asynchronous RPC Single-Page Model Config Getting Panels Widgets started DEMO #1 Raw DEMO #2 Binding Using Building Data Grids Views UI-Binding Styles / CSS Google Web Toolkit Application Error Extending Building Handling GWT Presenters Security Dashboard Talking to History the Server Pages Search View FormBryan Basham – The Google Web Toolkit Slide 51
  • 52. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Presenters == Business Logic ● Application-level logic – Security constraints – Page management and flow ● Page-level logic – Data management & caching – Validation – Server communication ● Widget-level logicBryan Basham – The Google Web Toolkit Slide 52
  • 53. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Application Entry-Point ● GWT starts up and invokes the onModuleLoad method on the apps entry-point class: «GWT EntryPoint» «RPC» GWT onModuleLoad Application getInitialState ApplicationRPC «DTO» «DTO» Application UserDTO StateDTO username : String environment : String roles : Set<RoleDTO> version : String user : UserDTO properties : Map<S,S>Bryan Basham – The Google Web Toolkit Slide 53
  • 54. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Application Entry-Point public class Application implements EntryPoint { private static final ApplicationRPCAsync appRPC = ApplicationRPC.Util.getInstance(); private static ApplicationStateDTO APP_STATE; public final void onModuleLoad() { initializeGUI(); } public final void initializeGUI() { appRPC.getIntialState(new CallbackAdaptor<ApplicationStateDTO>() { public void onSuccess(final ApplicationStateDTO appState) { APP_STATE = appState; propertyMap = appState.getPropertyMap(); buildNewGUI(); navigateToStartLocation(appState.getPlaceToStartCommand()); }; }); } private void buildNewGUI() { homePage = new AlloyHomePage(null); view = new ApplicationView(homePage); // Add the application View directly to the HTML <body> RootLayoutPanel.get().add(view); DOM.getElementById("alloy-loading").getStyle().setDisplay(Display.NONE); } }Bryan Basham – The Google Web Toolkit Slide 54
  • 55. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Error Handling ● RPC calls can throw Java exceptions ● The AsyncCallback interface provides the onFailure method hook ● The application can register an exception handler to handle these globallyBryan Basham – The Google Web Toolkit Slide 55
  • 56. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Error Handling public final void onModuleLoad() { // Save the Singleton instance created by GWT INSTANCE = this; GWT.setUncaughtExceptionHandler(new UncaughtExceptionHandler() { public void onUncaughtException(final Throwable error) { if (error != null) { if (error.getCause() != null) { ErrorPopup.center(error.getCause()); } else { ErrorPopup.center(error); } } else { Log.error("Uncaught exception, but error is null"); } } }); initializeGUI(); }Bryan Basham – The Google Web Toolkit Slide 56
  • 57. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Alloys Error PopupBryan Basham – The Google Web Toolkit Slide 57
  • 58. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Application Security ● The Application Singleton provides security query methods: public static boolean isUserInAnyRole(final RoleDTO... roles) { for (RoleDTO role : roles) { if (getCurrentUser().hasRole(role)) { return true; } } return false; } ● Which are then used by Header to filter out specific menus or disable menu items: private MenuBar createMenuBar() { MenuBar menubar = new MenuBar(); if (Application.isUserInRole(RoleDTO.ROLE_MONITOR)) { menubar.addMenu(createProspectiveMenu()); }Bryan Basham – The Google Web Toolkit Slide 58
  • 59. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Types of Pages ● Alloy uses four basic types of pages: – Dashboards: shows high-level status & system health – Monitor: view detail status and content flow – Control: modify system properties and other actions – Search: tools to search on document content ● AbstractPage and AbstractDashboardPage provide basic features of Alloy pagesBryan Basham – The Google Web Toolkit Slide 59
  • 60. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy GWT History Management ● Because a GWT app is a single HTML page, user navigation is a little tricky – The browsers back button, for example, will normally take you back to the page before you entered the GWT app – GWT provides a hidden <iframe> to manage an internal view transition management – GWT v2 provides a formal Places/Activities framework, but not used in AlloyBryan Basham – The Google Web Toolkit Slide 60
  • 61. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Alloys History Management «History Listener» GWT 1:onValueChange HistoryCmd Manager e ag eP ge ang Pa h ge T oC an k h 2:o 4:c 5:displayPage «Singleton» «Presenter» Application Page2 7:setState 3:isDirty 6:execute 8:reset currentPage «Presenter» Page2History Page1 ChangeHandlerBryan Basham – The Google Web Toolkit Slide 61
  • 62. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Talking to the Server View Presenter Model Like Java RMI MVP ...but asynchronous RPC Single-Page Model Config Getting Panels Widgets started DEMO #1 Raw DEMO #2 Binding Using Building Data Grids Views UI-Binding Styles / CSS Google Web Toolkit Application Error Extending Building Handling GWT Presenters Security Dashboard Talking to History the Server Pages Thinking Search Asynchronously Spring Integration View Form DTOs & Callback Transformers AdaptorBryan Basham – The Google Web Toolkit Slide 62
  • 63. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Spring Integration ● Remember the architecture: Desktop Internet Server Database User ........... ........... ........... use gen L SQ r ac e rate tion sH GWTs serialization and RPC protocol delegate to CRUD s ll s TM ca RPC over Ajax/HTTP requests RPC Service DAO rv er L se /async /impl s» a te re Boundary reads «c into client-side DTO transforms DTO Transformer Entity GWT Components Spring ComponentsBryan Basham – The Google Web Toolkit Slide 63
  • 64. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Spring Integration ● Based upon a blog post by Chris Lee: GWT-RPC with Spring 2.x ● Uses annotations on POJOs that implement the RPC sync interface: @GwtRpcEndPoint public class ProspectiveRPCImpl implements ProspectiveRPC { ... } ● Eliminates web.xml configuration of RPC servlets but must include simple declarations in dispatcher-servlet.xml (see next slide)Bryan Basham – The Google Web Toolkit Slide 64
  • 65. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Spring Integration <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" ...> <bean class="org.springframework.web.servlet.mvc.anno.AnnoMethodHandlerAdapter" /> <bean id="gwtAnnotationHandlerMapping" class="com.tr.cmg.alloy.ui.server.rpc.GwtAnnotationHandlerMapping" p:suffix=".rpc" p:prefix="/application/" p:order="1" /> <bean id="urlMapping" class="org.springframework.web.servlet.mvc.annno.DefaultAnnoHandlerMapping" p:order="2" /> <bean id="warehouseRPC" class="com.tr.cmg.alloy.ui.server.rpc.warehouse.WarehouseRPCImpl" /> <bean id="delDatamartRPC" class="com.tr.cmg.alloy.ui.server.rpc.datamart.delivery.DelDatamartRPCImpl" /> <!-- Many others... --> </beans>Bryan Basham – The Google Web Toolkit Slide 65
  • 66. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Callback Adaptor ● The AsyncCallback has two methods: – onSuccess: successful response – onFailure: any HTTP error or service exception ● But usually, you only care about onSuccess ● So... create a CallbackAdaptor which implements the onFailure method – This is a good place to handle any expected exceptions, such as security exceptions – Let unexpected exceptions be handled by the Application-level global handlerBryan Basham – The Google Web Toolkit Slide 66
  • 67. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Callback Adaptor public abstract class CallbackAdaptor<T> implements AsyncCallback<T> { @Override public void onFailure(final Throwable exception) { if (exception instanceof SessionTimeoutException) { MessageDialog.show(SESSION_TIMEOUT_MSG, GWT_RELOAD_ACTION); } else if (exception instanceof GWTSecurityException) { SecurityDialog.show((GWTSecurityException) exception, securityAction); } else if (is404(exception)) { MessageDialog.show(APPLICATION_REBOOT_MSG); } else if (exception.getMessage().contains(JDBC_CONNECTION_ERROR)) { String message = "A connection to the database could not be established"; String alert = "Database Connection Alert"; ErrorPopup.center(alert, message, exception); } else { ErrorPopup.center(exception); } } @Override public abstract void onSuccess(T result); }Bryan Basham – The Google Web Toolkit Slide 67
  • 68. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Callback Decorators ● Do not embed too much extraneous logic in callback methods ● Rather use the Decorator pattern to wrap the primary callback with additional behavior ● Alloy provides several decorators: – ServerWaitDecorator: provides the screen mask – LoggingDecorator: provides logging of the result – Lots of other possibilities...Bryan Basham – The Google Web Toolkit Slide 68
  • 69. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Callback Decorator Example public final class ServerWaitDecorator<T> extends CallbackDecorator<T> { public ServerWaitDecorator(final String maskText, final AsyncCallback<T> wrappedCallback) { super(Preconditions.checkNotNull(wrappedCallback)); // Start the masking process startMask(maskText); } @Override public void onFailure(final Throwable caught) { stopMask(); // Stop the masking process super.onFailure(caught); } @Override public void onSuccess(final T result) { stopMask(); // Stop the masking process super.onSuccess(result); } private void startMask(final String maskText) { ServerWaitPopup.show(maskText); } private void stopMask() { ServerWaitPopup.hide(); } }Bryan Basham – The Google Web Toolkit Slide 69
  • 70. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Callback Decorator Example // request settings from the Server AsyncCallback<List<FeedLoadSettingsDTO>> callbackChain = new CallbackAdaptor<List<FeedLoadSettingsDTO>>() { @Override public void onSuccess(final List<FeedLoadSettingsDTO> result) { settings = result; getView(FeedLoadSettingsView.class).setSettingsData(settings); // reset book-keeping data structures resetChanges(); } }; // decorate the callback callbackChain = new ServerWaitDecorator<List<FeedLoadSettingsDTO>>( "Retrieving settings; please wait...", callbackChain); // invoke the RPC call prospectiveRPC.retrieveFeedLoadSettings(Arrays.asList(collections), callbackChain);Bryan Basham – The Google Web Toolkit Slide 70
  • 71. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy DTOs and Transformers ● You could pass server-side Entity POJOs across the GWT RPC “wire”, but... – Muddies Entities with GWT-specific interface – Must be in the “GWT shared” package ● If you are using Hibernate and/or JPA, then Entity POJOs are decorated with objects that are not GWT-serializable ● Thus, I recommend separating Entity and DTOs and using transformers at the RPC tierBryan Basham – The Google Web Toolkit Slide 71
  • 72. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy DTOs and Transformers ● Furthermore, there could be real differences between the Entity and DTO classes – Entity classes might have much more data than the client-side DTO needs – DTO might have methods that are unique to the needs of the View or Presenter components ● Transformers are basically implementations of the Guava Function<T,F> interface – Only need to transform individuals – Let GWTs serializer handle lists, sets, maps, etcBryan Basham – The Google Web Toolkit Slide 72
  • 73. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Transformer Example @GwtRpcEndPoint public class ProspectiveRPCImpl implements ProspectiveRPC { @Autowired private ProspectiveService proService; private final Function<ProspectiveHealth, ProspectiveHealthDTO> healthTFM = new Function<ProspectiveHealth, ProspectiveHealthDTO>() { @Override public ProspectiveHealthDTO apply(@Nullable ProspectiveHealth input) { return new ProspectiveHealthDTO( input.getRequestProcessorHost(), input.getWorkGeneratorHost(), input.getFinalizerHost(), input.getCleanupHost(), input.getCleanupTimestamp()); } }; public ProspectiveHealthDTO getStationHealth(boolean forceRefresh) { // Delegate to Service ProspectiveHealth entity = proService.getStationHealth(forceRefresh); // Transform to client-side representation return healthTFM.apply(entity); } }Bryan Basham – The Google Web Toolkit Slide 73
  • 74. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Thinking Asynchronously ● Any event-driven environment, such as a user interface, requires the developer to think asynchronously. ● A web UI is doubly so because of the use of Ajax and therefore callbacks to handle the server response. ● Whats wrong with this? public void preparePage(final HistoryCommand pendingHistoryCmd) { retrievePathwayConfigFromServer(); retrieveUserGroupsFromServer(); sendSearchRequestToServer(); }Bryan Basham – The Google Web Toolkit Slide 74
  • 75. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Thinking Asynchronously public void preparePage(final HistoryCommand pendingHistoryCmd) { AsyncCallback<PathwayConfigurationDTO> firstCallback = new CallbackAdaptor<PathwayConfigurationDTO>() { @Override public void onSuccess(PathwayConfigurationDTO result) { // store pathway config pathwayConfig = result; // next get the user groups AsyncCallback<List<UserGroupDTO>> secondCallback = new CallbackAdaptor<List<UserGroupDTO>>() { @Override public void onSuccess(List<UserGroupDTO> result) { // store user groups userGroups = result; // finally send the search request AsyncCallback<List<String>> thirdCallback = new CallbackAdaptor<List<String>>() { @Override public void onSuccess(List<String> result) { // send search to the View getView().displaySearch(result); } }; myRPC.sendSearchRequest(thirdCallback); } }; myRPC.retrievePathwayConfig(secondCallback); } }; myRPC.retrievePathwayConfig(firstCallback); }Bryan Basham – The Google Web Toolkit Slide 75
  • 76. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Extending GWT View Presenter Model Like Java RMI MVP ...but asynchronous RPC Single-Page Model Config Getting Panels Widgets started DEMO #1 Raw DEMO #2 Binding Using Building Data Grids Views UI-Binding Styles / CSS Google Web Toolkit Dialog Application Gadgets Error Extending Building Handling Complex Widget GWT Presenters Security Simple Dashboard Widget Talking to History Widgets & the Server Pages Gadgets Thinking Search Asynchronously Spring Integration View Form DTOs & Callback Transformers AdaptorBryan Basham – The Google Web Toolkit Slide 76
  • 77. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Widgets and Gadgets ● Every non-trivial application will need to have custom widgets and gadgets. ● My definitions: – A widget is a GUI component that is meant to be embedded within other GUI components or panels, such as a custom drop-down list – A gadget is a standalone GUI component, such as a dialog boxBryan Basham – The Google Web Toolkit Slide 77
  • 78. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy A Simple Widget ● GWTs ListBox widget does not support rich, HTML text in the options, so I build one that does: /** * An enhancement to the GWT {@link ListBox} widget in which the option text is * rendered as raw HTML. This allows the developer to provide rich text in the * drop-down list. */ public class AlloyListBox extends ListBox { @Override protected void setOptionText(OptionElement opt, String text, Direction dir) { opt.setInnerHTML(text); } }Bryan Basham – The Google Web Toolkit Slide 78
  • 79. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy A Complex Widget – Problem ● GWTs ListBox widget only supports text values. It just mimics the HTML <select> element. For example: @UiField ListBox userGroupList; @UiHandler("userGroupList") The getItemText method final void selectUserGroup(final ChangeEvent e) { returns a String. String group; if (userGroupList.getSelectedIndex() == 0) { group = null; } else { group = userGroupList.getItemText(userGroupList.getSelectedIndex()); } getPage(FeedLoadSettingsPage.class).setSelectedGroup(group); // update Product list List<String> products = null; if (group != null) { products = getPage(FeedLoadSettingsPage.class).getProducts(group); } initProductList(products); }Bryan Basham – The Google Web Toolkit Slide 79
  • 80. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy A Complex Widget – Solution ● So, I built a drop-down widget that allows the developer to hold any type of object. ● Example now transformed to: The getValue method @UiField DropDownList<UserGroupDTO> userGroupList; returns an Object of the @UiHandler("userGroupList") the desired type. final void selectUserGroup(final ValueChangeEvent<UserGroupDTO> event) { UserGroupDTO group = event.getValue(); getPage(FeedLoadSettingsPage.class).setSelectedGroup(group); // update Product list List<String> products = null; if (group != null) { products = getPage(FeedLoadSettingsPage.class).getProducts(group); } initProductList(products); }Bryan Basham – The Google Web Toolkit Slide 80
  • 81. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy DropDownList – Implementation ● DropDownList list management: public class DropDownList<T> extends FocusWidget Favor composition implements HasValueChangeHandlers<T> { over inheritance. private final ListBox selectWidget; private T selectedItem; private Function<T, String> valueFunction; private Function<T, String> textFunction; private final Map<String, T> valueToItemMap = Maps.newHashMap(); public void addItem(T item) { String text = textFunction.apply(item); String value = valueFunction.apply(item); // validate a non-empty value (reserved for "null" item) if (value.isEmpty()) { throw new IllegalArgumentException("The Option " + text + " has an empty value."); } // add to <select> element and book-keeping selectWidget.addItem(text, value); valueToItemMap.put(value, item); } }Bryan Basham – The Google Web Toolkit Slide 81
  • 82. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy DropDownList – Implementation ● DropDownList declaration: The internal ListBox public class DropDownList<T> extends FocusWidget change handler dispatches implements HasValueChangeHandlers<T> { to the DropDownList private final ListBox selectWidget; private ListBox makeListBox() { value change handlers. ListBox widget = new ListBox(false); widget.addChangeHandler(new ChangeHandler() { public void onChange(ChangeEvent event) { final int selectedIdx = selectWidget.getSelectedIndex(); final String valueText = selectWidget.getValue(selectedIdx); if (Strings.isNullOrEmpty(valueText)) { fireValueChangeEvent(null); } else { fireValueChangeEvent(valueToItemMap.get(valueText)); } } }); return widget; } private void fireValueChangeEvent(final T newValue) { ValueChangeEvent.fireIfNotEqual(this, selectedItem, newValue); selectedItem = newValue; } }Bryan Basham – The Google Web Toolkit Slide 82
  • 83. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Dialog Gadgets ● GWT comes with a PopupPanel component but nothing like a “confirmation dialog.”Bryan Basham – The Google Web Toolkit Slide 83
  • 84. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Confirmation Dialog Example ● The Prospective Dashboard popup menu uses the confirmation dialog like so: pauseItem = addMenuItem(PAUSE_MENU_LABEL, new Command() { public void execute() { ConfirmationDialog.show(PAUSE_CONFIRMATION_MSG, pauseCmd); } }, Application.CONTROL_ROLES); ● The ConfirmationDialog is a Singleton with static method API to show the dialog. – It takes a message and a GWT Command object that is invoke if the user clicks the “Ok” button.Bryan Basham – The Google Web Toolkit Slide 84
  • 85. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Confirmation Dialog Example public final class ConfirmationDialog { public interface Callback { void onOk(); void onCancel(); } public abstract static class SimpleCallback implements Callback { public void onCancel() { } } // Singleton pattern private static final ConfirmationDialog INSTANCE = new ConfirmationDialog(); static { RootPanel.get().add(INSTANCE.view); } private ConfirmationDialog() { } // Attributes private final ConfirmationView view = new ConfirmationView(); // The “show” methods on next slide }Bryan Basham – The Google Web Toolkit Slide 85
  • 86. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Confirmation Dialog Example public final class ConfirmationDialog { // More code on previous slide public static void show(final String title, final String message, final Callback callback) { INSTANCE.view.show(title, message, callback); } public static void show(final String message, final Callback callback) { show("Please Confirm", message, callback); } public static void show(final String message, final Command okCommand) { show("Please Confirm", message, new SimpleCallback() { @Override public void onOk() { okCommand.execute(); } }); } }Bryan Basham – The Google Web Toolkit Slide 86
  • 87. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Confirmation Dialog View public class ConfirmationView extends AlloyPopupPanel { private static final String DIALOG_ID = "confirmationDialog"; interface MyUiBinder extends UiBinder<VerticalPanel, ConfirmationView> { } private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class); @UiField Label titleLabel; @UiField HTML messageText; @UiField Button okButton; @UiField Button cancelButton; ConfirmationView() { super(false, true); setWidget(uiBinder.createAndBindUi(this)); getElement().setId(DIALOG_ID); getElement().getStyle().setWidth(250, Style.Unit.PX); } final void show(String title, String message, Callback c) { titleLabel.setText(title); messageText.setWordWrap(true); messageText.setHTML(message); callback = c; center(); } // The button handler methods on next slideBryan Basham – The Google Web Toolkit Slide 87
  • 88. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Confirmation Dialog View public class ConfirmationView extends AlloyPopupPanel { // More code on previous slide @UiHandler("okButton") final void handleOk(final ClickEvent e) { callback.onOk(); hide(); } @UiHandler("cancelButton") final void handleCancel(final ClickEvent e) { callback.onCancel(); hide(); } } <ui:UiBinder xmlns:ui=urn:ui:com.google.gwt.uibinder xmlns:g=urn:import:com.google.gwt.user.client.ui> <g:VerticalPanel> <g:Label ui:field="titleLabel" styleName="title">Please Confirm</g:Label> <g:HTML ui:field="messageText" styleName="body" /> <g:FlowPanel styleName="buttons"> <g:Button ui:field="okButton">Ok</g:Button> <g:Button ui:field="cancelButton">Cancel</g:Button> </g:FlowPanel> </g:VerticalPanel> </ui:UiBinder>Bryan Basham – The Google Web Toolkit Slide 88
  • 89. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Alloys Popup Panel Class public abstract class AlloyPopupPanel extends PopupPanel { protected AlloyPopupPanel(final boolean autoHide, final boolean modal) { super(autoHide, modal); addStyleName("AlloyPopup"); } } // in Application.css (provides the border and drop-shadow effect) div.AlloyPopup { background-color: #FFFFFF; border-color: #CECECE #BABABA #888888 #CECECE; border-width: 1px; border-style: solid; border-radius: 7px; box-shadow: 10px 10px 5px #75b2d5; }Bryan Basham – The Google Web Toolkit Slide 89
  • 90. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Using Data Grids View Presenter Model Like Java RMI MVP ...but asynchronous RPC Single-Page Model Config Getting Panels Widgets Edit Grids started DEMO #1 Raw Paging DEMO #2 Binding Using Building Data Grids Views Sorting UI-Binding DataGrid Basics Styles / CSS Google Web Toolkit Dialog Application Gadgets Error Extending Building Handling Complex Widget GWT Presenters Security Simple Dashboard Widget Talking to History Widgets & the Server Pages Gadgets Thinking Search Asynchronously Spring Integration View Form DTOs & Callback Transformers AdaptorBryan Basham – The Google Web Toolkit Slide 90
  • 91. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy The DataGrid Basics ● Two GWT widgets for HTML tables: – CellTable is an ordinary <table> – DataGrid is a resize-able grid where the body is scrollable ● Grids may have headers and footers ● Grid columns may support sorting ● Grids may support client-side and server-side paging using the SimplePager widget ● Grids can be editableBryan Basham – The Google Web Toolkit Slide 91
  • 92. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy The DataGrid Basics R is the data type of the C is the data type of the contents of each row in the grid. contents of a single column. R R, C C 1..* 1 DataGrid Column Cell columns cell R R, C=String C=Number ... ... AlloyDataGrid TextColumn NumberCellBryan Basham – The Google Web Toolkit Slide 92
  • 93. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Example Simple Grid Header FooterBryan Basham – The Google Web Toolkit Slide 93
  • 94. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Example Simple Grid public class MonitorView extends AbstractPageView { @UiField(provided = true) AlloyDataGrid<MonitorOverviewDTO> overviewGrid; void populateOverviewGrid(final List<MonitorOverviewDTO> dtos) { overviewGrid.setRowData(dtos); } private void initialize() { // initialize Overview tab grid overviewGrid = new AlloyDataGrid<MonitorOverviewDTO>("overviewGrid"); overviewGrid.setWidth("75%"); // Date column TextColumn<MonitorOverviewDTO> dateColumn = new TextColumn<...>() { @Override public String getValue(final MonitorOverviewDTO row) { return DateUtils.asString(row.getDate()); } }; dateColumn.setCellStyleNames("columnLeft"); overviewGrid.addColumn(dateColumn, "Date"); overviewGrid.setColumnWidth(dateColumn, DATE_COLUMN_SIZE, Unit.PCT); // ... more column definitions on next slide }Bryan Basham – The Google Web Toolkit Slide 94
  • 95. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Simple Grid – Column w/ Footer public class MonitorView extends AbstractPageView { private void initialize() { // column definition with Footer Column<MonitorOverviewDTO, Number> workflowsColumn = new Column<MonitorOverviewDTO, Number>( new NumberCell()) { @Override public Number getValue(final MonitorOverviewDTO row) { return row.getWorkflows(); } }; Header<Number> workflowsFooter = new Header<Number>(new NumberCell()) { public Number getValue() { A footer is int sum = 0; defined by a for (MonitorOverviewDTO row : overviewGrid.getVisibleItems()) { sum += row.getWorkflows(); Header object. } Go figure! return sum; } }; overviewGrid.addColumn(workflowsColumn, new SafeHtmlHeader(SafeHtmlUtils.fromTrustedString("Workflows")), workflowsFooter); overviewGrid.setColumnWidth(workflowsColumn, WORKFLOW_COL_SIZE, Unit.PCT); }Bryan Basham – The Google Web Toolkit Slide 95
  • 96. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Grids with Sortable Columns ● Grid columns may support sorting ● Grid contains a “sort handler” ● Use a “data provider” and sort its list using a Java Comparator – Need a comparator for the whole grid; must handle ascending and descending – Need a comparator for each column that supports sortingBryan Basham – The Google Web Toolkit Slide 96
  • 97. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Grid Sorting – Screenshot Ascending iconBryan Basham – The Google Web Toolkit Slide 97
  • 98. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Grid Sorting – Data Provider public class MonitorView extends AbstractPageView { Create a data provider. @UiField(provided = true) AlloyDataGrid<MonitorOverviewDTO> overviewGrid; private final ListDataProvider<MonitorOverviewDTO> dataProvider = new ListDataProvider<MonitorOverviewDTO>(); void populateOverviewGrid(final List<MonitorOverviewDTO> dtos) { // sort new list Collections.sort(dtos, gridComparator); // refresh the view dataProvider.setList(dtos); Sort the data provider. overviewGrid.setVisibleRange(0, dtos.size()); } private void initialize() { // initialize Overview tab grid overviewGrid = new AlloyDataGrid<MonitorOverviewDTO>("overviewGrid"); overviewGrid.setWidth("75%"); overviewGrid.addColumnSortHandler(contentSortHandler); dataProvider.addDataDisplay(overviewGrid); Attach grid to data provider. Attach a column sort handler.Bryan Basham – The Google Web Toolkit Slide 98
  • 99. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Grid Sorting – Grid Comparator public class MonitorView extends AbstractPageView { private static class GridSortComparator implements Comparator<MonitorOverviewDTO> { // default sort: by Date column ascending private ColumnComparator columnComp = ColumnComparator.DATE; private boolean isAscending = true; @Override public int compare(MonitorOverviewDTO o1, MonitorOverviewDTO o2) { int ascCompare = columnComp.compare(o1, o2); if (!isAscending) { ascCompare *= -1; // reverse if descending } return ascCompare; } } private final GridSortComparator gridComparator = new GridSortComparator();Bryan Basham – The Google Web Toolkit Slide 99
  • 100. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Grid Sorting – Column Comparators as an Enum public class MonitorView extends AbstractPageView { enum ColumnComparator implements Comparator<MonitorOverviewDTO> { DATE() { public int compare(MonitorOverviewDTO o1, MonitorOverviewDTO o2) { return o1.getDate().compareTo(o2.getDate()); } }, WORKFLOWS() { public int compare(MonitorOverviewDTO o1, MonitorOverviewDTO o2) { return o1.getWorkflows() - o2.getWorkflows(); } }, DOCUMENTS() { public int compare(MonitorOverviewDTO o1, MonitorOverviewDTO o2) { return (int) (o1.getDocuments() - o2.getDocuments()); } } }Bryan Basham – The Google Web Toolkit Slide 100
  • 101. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Grid Sorting – Sort Handler public class MonitorView extends AbstractPageView { private final ColumnSortEvent.Handler contentSortHandler = new Handler() { @Override public void onColumnSort(final ColumnSortEvent event) { // determine which column is being sorted if (event.getColumn() == dateColumn) { gridComparator.columnComp = ColumnComparator.DATE; } else if (event.getColumn() == workflowsColumn) { gridComparator.columnComp = ColumnComparator.WORKFLOWS; } else if (event.getColumn() == documentsColumn) { gridComparator.columnComp = ColumnComparator.DOCUMENTS; } else { Log.error("Unknown column to sort on: " + event.getColumn()); } gridComparator.isAscending = event.isSortAscending(); // sort the data provider List<MonitorOverviewDTO> gridData = dataProvider.getList(); Collections.sort(gridData, gridComparator); // refresh the grid via the data provider dataProvider.refresh(); } };Bryan Basham – The Google Web Toolkit Slide 101
  • 102. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Grid Sorting – Declare Columns public class MonitorView extends AbstractPageView { private TextColumn<MonitorOverviewDTO> dateColumn; private Column<MonitorOverviewDTO, Number> workflowsColumn; private Column<MonitorOverviewDTO, Number> documentsColumn; private void initialize() { // Date column dateColumn = new TextColumn<MonitorOverviewDTO>() { @Override public String getValue(final MonitorOverviewDTO row) { return DateUtils.asString(row.getDate()); } }; dateColumn.setSortable(true); Declare column is sortable. dateColumn.setCellStyleNames("columnLeft"); overviewGrid.addColumn(dateColumn, "Date"); overviewGrid.setColumnWidth(dateColumn, FIRST_COL_SIZE, Unit.PCT);Bryan Basham – The Google Web Toolkit Slide 102
  • 103. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Grid Paging ● Paging requires a data provider ● Paging requires an additional widget: such as a SimplePager ● Paging might be client-side or server-side – Server-side paging uses an AsyncDataProvider which performs incremental RPC calls when changing pages – Client-side paging can just use a simple ListDataProviderBryan Basham – The Google Web Toolkit Slide 103
  • 104. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Example Paged Grid Simple paging controls.Bryan Basham – The Google Web Toolkit Slide 104
  • 105. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Example Paged Grid public class LinkAnalysisView extends AbstractPageView { @UiField(provided = true) DataGrid<RrdResultDTO> erdNovusLinksGrid; @UiField SimplePager erdGridPager; private AsyncDataProvider<RrdResultDTO> linksDataProvider; public LinkAnalysisView(AbstractPage pageIn) { super(pageIn); erdNovusLinksGrid = new AlloyDataGrid<RrdResultDTO>(makeFieldId("grid")); erdNovusLinksGrid.setWidth("75%"); erdGridPager.setDisplay(erdNovusLinksGrid); erdGridPager.setPageSize(PAGE_SIZE); }Bryan Basham – The Google Web Toolkit Slide 105
  • 106. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Example Paged Grid public class LinkAnalysisView extends AbstractPageView { void erdSetRrdResults(final QueryResultDTO<RrdResultDTO> rrdResults) { erdNovusLinksGrid.setRowCount(rrdResults.getTotalCount(), true); erdNovusLinksGrid.setVisibleRange(0, PAGE_SIZE); if (rrdResults.getItems().size() == 0) { Custom data provider erdNovusLinksGrid.setRowData(rrdResults.getItems()); } else { (see next slide) // create the data provider that performs server-side paging linksDataProvider = new RrdSearchDataProvider(erdGuidField.getText()){ @Override protected void sendMatchRequest( String guid, QueryRequestDTO queryInfo, AsyncCallback<QueryResultDTO<RrdResultDTO>> callback) { LinkAnalysisRPC.Util.getInstance().matchErdGUID( guid, queryInfo, callback); } }; The RPC call // connect the data provider to the grid linksDataProvider.addDataDisplay(erdNovusLinksGrid); } }Bryan Basham – The Google Web Toolkit Slide 106
  • 107. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Custom Async Data Provider public abstract class RrdSearchDataProvider extends AsyncDataProvider<RrdResultDTO> { private final String guid; public RrdSearchDataProvider(final String guid) { this.guid = guid; } protected void onRangeChanged(final HasData<RrdResultDTO> display) { // use the new grid Range to request a page from the server final Range range = display.getVisibleRange(); // build the paged request final QueryRequestDTO queryInfo = new QueryRequestDTO(range.getStart(), range.getLength()); // build the callback chain AsyncCallback<QueryResultDTO<RrdResultDTO>> callbackChain = new CallbackAdaptor<QueryResultDTO<RrdResultDTO>>() { public final void onSuccess(final QueryResultDTO<RrdResultDTO> results) { // when the server response comes, update the grid updateRowData(range.getStart(), results.getItems()); // set the total row count display.setRowCount(results.getTotalCount()); } }; // send the request to the server sendMatchRequest(guid, queryInfo, callbackChain); } protected abstract void sendMatchRequest( String guid, QueryRequestDTO queryInfo, AsyncCallback<QueryResultDTO<RrdResultDTO>> callback); }Bryan Basham – The Google Web Toolkit Slide 107
  • 108. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Editable Grids ● Grid cells (and header) can contain edit widgets, such as text fields, check boxes, and list boxes ● Use a FieldUpdater component on the column definition ● The application must manage how the changes are sent to the server; its not automaticBryan Basham – The Google Web Toolkit Slide 108
  • 109. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Example Editable Grid Column checkbox Highlights when changed. Page save/reset buttonsBryan Basham – The Google Web Toolkit Slide 109
  • 110. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Example Editable Grid public class FeedLoadSettingsView extends AbstractPageView { @UiField(provided = true) AlloyDataGrid<FeedLoadSettingsDTO> settingsGrid; private void initialize() { settingsGrid = new AlloyDataGrid<FeedLoadSettingsDTO>("settingsGrid"); // Feed? Column final FieldUpdater<FeedLoadSettingsDTO, Boolean> feedUpdater = new FieldUpdater<FeedLoadSettingsDTO, Boolean>() { @Override public void update(final int index, final FeedLoadSettingsDTO row, final Boolean value) { InputElement checkboxEl = findCheckboxCellField(index, FEED_CELL_INDEX); checkboxEl.setChecked(value.booleanValue()); row.updateFeeding(value); Store the change if (row.isFeedingDirty()) { checkboxEl.addClassName(HtmlUtils.FIELD_CHANGED_CLASS); } else { checkboxEl.removeClassName(HtmlUtils.FIELD_CHANGED_CLASS); } evaluateSettingsStatus(); Enable/disable the Save } }; & Reset buttons.Bryan Basham – The Google Web Toolkit Slide 110
  • 111. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Example Editable Grid Custom cell widget public class FeedLoadSettingsView extends AbstractPageView { private final CheckboxCell feedCheckboxCell = new SecureCheckboxCell( RoleDTO.ROLE_SUPER_CONTROL); private void initialize() { settingsGrid = new AlloyDataGrid<FeedLoadSettingsDTO>("settingsGrid"); // Feed? Column (more) final FieldUpdater<FeedLoadSettingsDTO, Boolean> feedUpdater = ...; Column<FeedLoadSettingsDTO, Boolean> feedCol = new Column<FeedLoadSettingsDTO, Boolean>(feedCheckboxCell) { @Override public Boolean getValue(final FeedLoadSettingsDTO row) { return row.isFeeding(); } }; feedCol.setFieldUpdater(feedUpdater); feedCol.setCellStyleNames("columnCenter"); settingsGrid.addColumn(feedCol, feedHdr); settingsGrid.setColumnWidth(feedCol, FEED_COL_SIZE, Unit.PCT);Bryan Basham – The Google Web Toolkit Slide 111
  • 112. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Custom Editable Cell Widget public class SecureCheckboxCell extends CheckboxCell { private static final String ENABLED_AND_CHECKED = "<input type="checkbox" tabindex="-1" checked />"; private static final String ENABLED_AND_UNCHECKED = "<input type="checkbox" tabindex="-1" />"; private static final String DISABLED_AND_CHECKED = "<input type="checkbox" tabindex="-1" checked disabled="disabled" />"; private static final String DISABLED_AND_UNCHECKED = "<input type="checkbox" tabindex="-1" disabled="disabled" />"; private final RoleDTO[] enabledForRoles; public SecureCheckboxCell(final RoleDTO... enabledForRolesIn) { super(false, false); enabledForRoles = enabledForRolesIn; } // continued on next slideBryan Basham – The Google Web Toolkit Slide 112
  • 113. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Custom Editable Cell Widget public class SecureCheckboxCell extends CheckboxCell { // continued from previous slide public void render(final Context context, final Boolean value, final SafeHtmlBuilder sb) { // Get the view data. Object key = context.getKey(); Boolean viewData = getViewData(key); if (viewData != null && viewData.equals(value)) { clearViewData(key); viewData = null; } if (value != null && ((viewData != null) ? viewData : value)) { if (Application.isUserInAnyRole(enabledForRoles)) { sb.append(SafeHtmlUtils.fromSafeConstant(ENABLED_AND_CHECKED)); } else { sb.append(SafeHtmlUtils.fromSafeConstant(DISABLED_AND_CHECKED)); } } else { if (Application.isUserInAnyRole(enabledForRoles)) { sb.append(SafeHtmlUtils.fromSafeConstant(ENABLED_AND_UNCHECKED)); } else { sb.append(SafeHtmlUtils.fromSafeConstant(DISABLED_AND_UNCHECKED)); } } } }Bryan Basham – The Google Web Toolkit Slide 113
  • 114. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Q&A View Presenter Model Like Java RMI MVP ...but asynchronous RPC Single-Page Model Config Getting Panels Widgets Edit Grids started DEMO #1 Raw Paging DEMO #2 Binding Using Building Data Grids Views Sorting UI-Binding DataGrid Basics Styles / CSS Google Web Toolkit Dialog Application Gadgets Error Extending Building Handling Complex Widget GWT Presenters Security Simple Dashboard Widget Talking to History Widgets & the Server Pages Gadgets Thinking Search Asynchronously Spring Integration View Form DTOs & Callback Transformers AdaptorBryan Basham – The Google Web Toolkit Slide 114
  • 115. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy LAST WORD: GWT is not Light-Weight ● As with any Web app, GWT requires a fair amount of infrastructure: r calls RPC serve user actions /async signals events generates HTML View sends data Presenter read User s DTO .... .. ..... .... View.ui.xml View.java Presenter.java DTO.java 3 RPC types .... .. ..... .... styles.cssBryan Basham – The Google Web Toolkit Slide 115
  • 116. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy 5 Great Topics (not covered) 1) Testing: Unit (eg, jUnit or TestNG) and Integration (eg, Selenium) 2) GWT history management using Places/Activities 3) Third-party libraries, some good (SmartGWT) and some great (GXT) 4) Cross-site scripting and GWTs SafeHtml 5) GWTs RequestFactory for simplified (yeah right) data accessBryan Basham – The Google Web Toolkit Slide 116
  • 117. RJUG : 12-Mar-2012 © Copyright 2013, Software Alchemy Resources ● Wikipedia ● Official GWT Home ● GWT Widget Gallery ● GWT API javadocsBryan Basham – The Google Web Toolkit Slide 117