Google Web Toolkit: a case study
-
Upload
bryan-basham -
Category
Software
-
view
2.643 -
download
5
description
Transcript of Google Web Toolkit: a case study
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 1
© Copyright 2013, Software Alchemy
Gettingstarted
BuildingViews
ExtendingGWT
Talking tothe Server
BuildingPresenters
UsingData Grids
GoogleWeb Toolkit
The Google Web Toolkit
Bryan BashamSoftware Alchemy
http://www.linkedin.com/in/SoftwareAlchemist
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 2
© Copyright 2013, Software Alchemy
Config
Single-PageModel
Like Java RMI
MVPRPC ...but asynchronous
ModelView
ModelPresenter
DEMO #1
Gettingstarted
BuildingViews
ExtendingGWT
Talking tothe Server
BuildingPresenters
UsingData Grids
GoogleWeb Toolkit
Getting Started...
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 3
© 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 Java's RMI
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 4
© Copyright 2013, Software Alchemy
Demo: “Hello World”
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 5
© Copyright 2013, Software Alchemy
UserDatabase
Desktop
.................................
Internet Server
Server-sideComponents
& Entities
Client-sideComponents
& DTOs
Typical GWT Architecture
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 6
© Copyright 2013, Software Alchemy
User
Boundary
RPC/async
DTO
RPC/impl
DatabaseDesktop
.................................
Internet Server
GWT Components
transfer objects (both directions)
reads
server calls GWT's serialization and RPC protocol
over Ajax/HTTP requests
user actions
generates HTML Other
Server-sideComponents
& Entities
Typical GWT Architecture
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 7
© Copyright 2013, Software Alchemy
User
Boundary
RPC/async
Entity
DAO
DTO
RPC/impl
DatabaseDesktop
.................................
Internet Server
GWT Components Spring Components
«cre
ates
»
transfer objects (both directions)
delegate to CRUD
SQL
reads
server ca
lls GWT's serialization and RPC protocolover Ajax/HTTP requests
user actions
generates HTML Service
?
Typical GWT Architecture
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 8
© Copyright 2013, Software Alchemy
User
Boundary
RPC/async
Entity
DAO
TransformerDTO
RPC/impl
DatabaseDesktop
.................................
Internet Server
GWT Components Spring Components
«cre
ates
»
transformsinto client-side DTO
delegate to CRUD
SQL
reads
server calls GWT's serialization and RPC protocol
over Ajax/HTTP requests
user actions
generates HTML Service
Typical GWT Architecture
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 9
© 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 resizes
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 10
© 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.
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 11
© 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>
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 12
© 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.
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 13
© Copyright 2013, Software Alchemy
Model-View-Presenter
● Similar to the popular Model-View-Controller pattern, but cleaner separation:
– 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 DTOs
User Presenter
RPC/async
DTO
reads
server calls
user actions
generates HTML View
signals events
sends data
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 14
© 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 GUI's 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 media
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 15
© 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>
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 16
© 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 slide
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 17
© 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 slide
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 18
© 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); }}
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 19
© 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-testing
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 20
© 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); } }); }}
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 21
© Copyright 2013, Software Alchemy
Data Transfer Objects (DTO)
● DTOs provide a client-side representation of the application's 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?
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 22
© Copyright 2013, Software Alchemy
Remote Procedure Calls (RPC)
● RPC components provide communication with the Server, much like Java's RMI
● GWT's 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 functions
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 23
© Copyright 2013, Software Alchemy
RPC (Architecture Diagram)
Boundary «class»MyRPCImpl
server calls GWT's serialization and RPC protocolover Ajax/HTTP requests«class»
MyRPCAsync_impl
«interface»MyRPCAsync
GWT RPCservlet
delegates to
«interface»MyRPC
This class implementation is providedby GWT which contains boiler-plate codeto make Ajax requests.
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 value
The Async interface is based upon theSync interface but with an additionalCallback<ReturnType> parameter.
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 24
© 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 GreetingServiceImplextends 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; }}
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 25
© 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>
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 26
© Copyright 2013, Software Alchemy
RPC (Interaction Diagram)
Boundary «class»MyRPCImpl
«class»MyRPCAsync_
impl
GWT RPCservlet
name:String“Fred”
result:String“Hello Fred”
callback:AsyncCallback<String>
sayHello(name, callback)
sayHello(name)
return
name:String“Fred”
result:String“Hello Fred”onSuccess(result)
GWT RPCinvoker
payload[sayHello|Fred]
payload[Hello Fred]
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 27
© 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 theprimary config file.
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 28
© 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>
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 29
© 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 sides
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 30
© Copyright 2013, Software Alchemy
Config
Single-PageModel
Like Java RMI
MVPRPC ...but asynchronous
ModelView
ModelPresenter
Styles / CSS
Widgets Panels
RawBinding
UI-Binding
DEMO #1DEMO #2
Gettingstarted
BuildingViews
ExtendingGWT
Talking tothe Server
BuildingPresenters
UsingData Grids
GoogleWeb Toolkit
Building Views
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 31
© Copyright 2013, Software Alchemy
DEMO #2
● Alloy is a Monitor & Control app on a document processing pathway:
● Also: search, reports, admin tools
document1
....
..
.....
....
Prospective Warehouse
UserDatamart1
Datamart2User
document2
....
..
.....
....
document3
....
..
.....
....
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 32
© 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 content
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 33
© Copyright 2013, Software Alchemy
DEMO #2: Dashboards
Header
Footer
Bread-crumb
PagesDeck
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 34
© Copyright 2013, Software Alchemy
DEMO #2: Monitor page
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 35
© Copyright 2013, Software Alchemy
DEMO #2: Control page
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 36
© 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 Gallery
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 37
© 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 DataGrid
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 38
© 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>
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 39
© Copyright 2013, Software Alchemy
«GWT Widget»Resize
Composite
«View»Abstract
PageViewview
1«Presenter»Abstract
Page
«View»Prospective
FeedLoadView
«Presenter»Prospective
FeedLoadPage
«GWT EntryPoint»Application pages
1..*
Extending this class forces each pageview to be resize-able. It is the developer'sresponsibility to decide how the page'slayout is constructed to support resizing.
... ...
Alloy's Core GUI Architecture
● Application contains many Pages with one View each:
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 40
© Copyright 2013, Software Alchemy
Raw UI-binding
● GWT provides full access to the page's 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)
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 41
© 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)
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 42
© Copyright 2013, Software Alchemy
Raw UI-binding Example
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 43
© 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>
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 44
© 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 }}
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 45
© 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) }}
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 46
© 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 View's widgets● Handle user events on the View's widgets
– Other CSS files:● Styles of more generic aspects of the UI● Styles of custom widgets
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 47
© 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'> <ui:style> .body { margin: 1em; } .button { margin-top: 0 !important; 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 --> </g:north> <g:center> <c:DataGrid ui:field='settingsGrid' /> </g:center> </g:DockLayoutPanel></ui:UiBinder>
Widget libraries
Page-specific CSS styles
Fixed-width dock region
Resize-able dock region
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 48
© 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>
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 49
© 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 slide
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 50
© 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 GWT's getStyle().setXyzProperty()
as a last resort
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 51
© Copyright 2013, Software Alchemy
Config
Single-PageModel
Like Java RMI
MVPRPC ...but asynchronous
ModelView
ModelPresenter
Styles / CSS
Widgets Panels
RawBinding
UI-Binding
History
Application
Form View
Search
Dashboard
Pages
DEMO #1DEMO #2
ErrorHandling
Security
Gettingstarted
BuildingViews
ExtendingGWT
Talking tothe Server
BuildingPresenters
UsingData Grids
GoogleWeb Toolkit
Building Presenters
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 52
© 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 logic
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 53
© Copyright 2013, Software Alchemy
Application Entry-Point
● GWT starts up and invokes the onModuleLoad method on the app's entry-point class:
GWT «GWT EntryPoint»Application
«DTO»UserDTO
username : Stringroles : Set<RoleDTO>
onModuleLoad getInitialState
«RPC»ApplicationRPC
«DTO»ApplicationStateDTO
environment : Stringversion : Stringuser : UserDTOproperties : Map<S,S>
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 54
© 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); }}
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 55
© 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 globally
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 56
© 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(); }
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 57
© Copyright 2013, Software Alchemy
Alloy's Error Popup
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 58
© 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()); }
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 59
© 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 pages
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 60
© Copyright 2013, Software Alchemy
GWT History Management
● Because a GWT app is a single HTML page, user navigation is a little tricky
– The browser's 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 Alloy
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 61
© Copyright 2013, Software Alchemy
Alloy's History Management
GWT1:onValueChange
«Presenter»Page1
2:okToChangePage
3:isDirty
4:changePage
«History Listener»HistoryCmd
Manager
currentPage
8:reset
«Singleton»Application
5:displayPage
6:execute
«Presenter»Page2
7:setState
Page2HistoryChangeHandler
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 62
© Copyright 2013, Software Alchemy
Config
Single-PageModel
Like Java RMI
MVPRPC ...but asynchronous
ModelView
ModelPresenter
Styles / CSS
Widgets Panels
RawBinding
UI-Binding
History
Application
Form View
Search
Dashboard
Pages
DEMO #1DEMO #2
ErrorHandling
Security
SpringIntegration
CallbackAdaptor
DTOs &Transformers
ThinkingAsynchronously
Gettingstarted
BuildingViews
ExtendingGWT
Talking tothe Server
BuildingPresenters
UsingData Grids
GoogleWeb Toolkit
Talking to the Server
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 63
© Copyright 2013, Software Alchemy
Spring Integration
● Remember the architecture:
User
Boundary
RPC/async
Entity
DAO
TransformerDTO
RPC/impl
DatabaseDesktop
.................................
Internet Server
GWT Components Spring Components
«cre
ates
»
transformsinto client-side DTO
delegate to CRUD
SQL
reads
server calls GWT's serialization and RPC protocol
over Ajax/HTTP requests
user actions
generates HTML Service
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 64
© 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:
@GwtRpcEndPointpublic class ProspectiveRPCImpl implements ProspectiveRPC { ... }
● Eliminates web.xml configuration of RPC servlets but must include simple declarations in dispatcher-servlet.xml (see next slide)
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 65
© 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>
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 66
© 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 handler
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 67
© 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);}
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 68
© 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...
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 69
© 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(); }}
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 70
© Copyright 2013, Software Alchemy
Callback Decorator Example
// request settings from the ServerAsyncCallback<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 callbackcallbackChain = new ServerWaitDecorator<List<FeedLoadSettingsDTO>>( "Retrieving settings; please wait...", callbackChain);
// invoke the RPC callprospectiveRPC.retrieveFeedLoadSettings(Arrays.asList(collections), callbackChain);
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 71
© 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 tier
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 72
© 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 GWT's serializer handle lists, sets, maps, etc
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 73
© Copyright 2013, Software Alchemy
Transformer Example
@GwtRpcEndPointpublic 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); }}
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 74
© 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.
● What's wrong with this? public void preparePage(final HistoryCommand pendingHistoryCmd) { retrievePathwayConfigFromServer(); retrieveUserGroupsFromServer(); sendSearchRequestToServer(); }
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 75
© 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); }
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 76
© Copyright 2013, Software Alchemy
Config
Single-PageModel
Like Java RMI
MVPRPC ...but asynchronous
ModelView
ModelPresenter
Styles / CSS
Widgets Panels
RawBinding
UI-Binding
History
Application
Form View
Search
Dashboard
Pages
DEMO #1DEMO #2
ErrorHandling
Security
SpringIntegration
CallbackAdaptor
DTOs &Transformers
ThinkingAsynchronously
Widgets &Gadgets
SimpleWidget
ComplexWidget
DialogGadgets
Gettingstarted
BuildingViews
ExtendingGWT
Talking tothe Server
BuildingPresenters
UsingData Grids
GoogleWeb Toolkit
Extending GWT
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 77
© 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 box
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 78
© Copyright 2013, Software Alchemy
A Simple Widget
● GWT's 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); }
}
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 79
© Copyright 2013, Software Alchemy
A Complex Widget – Problem
● GWT's ListBox widget only supports text values. It just mimics the HTML <select> element. For example:
@UiField ListBox userGroupList;
@UiHandler("userGroupList") final void selectUserGroup(final ChangeEvent e) { 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); }
The getItemText methodreturns a String.
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 80
© 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:
@UiField DropDownList<UserGroupDTO> userGroupList;
@UiHandler("userGroupList") 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); }
The getValue methodreturns an Object of thethe desired type.
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 81
© Copyright 2013, Software Alchemy
DropDownList – Implementation
● DropDownList list management:public class DropDownList<T> extends FocusWidgetimplements HasValueChangeHandlers<T> { 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); }}
Favor compositionover inheritance.
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 82
© Copyright 2013, Software Alchemy
DropDownList – Implementation
● DropDownList declaration:public class DropDownList<T> extends FocusWidgetimplements HasValueChangeHandlers<T> { private final ListBox selectWidget; private ListBox makeListBox() { 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; }}
The internal ListBoxchange handler dispatchesto the DropDownListvalue change handlers.
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 83
© Copyright 2013, Software Alchemy
Dialog Gadgets
● GWT comes with a PopupPanel component but nothing like a “confirmation dialog.”
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 84
© 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.
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 85
© 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}
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 86
© 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(); } }); }}
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 87
© 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 slide
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 88
© 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>
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 89
© Copyright 2013, Software Alchemy
Alloy's 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;}
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 90
© Copyright 2013, Software Alchemy
Config
Single-PageModel
Like Java RMI
MVPRPC ...but asynchronous
ModelView
ModelPresenter
Styles / CSS
Widgets Panels
RawBinding
UI-Binding
History
Application
Form View
Search
Dashboard
Pages
DEMO #1DEMO #2
ErrorHandling
Security
SpringIntegration
CallbackAdaptor
DTOs &Transformers
ThinkingAsynchronously
Widgets &Gadgets
SimpleWidget
ComplexWidget
DialogGadgets
ExtendingGWT
DataGridBasics
Sorting
Paging
Edit GridsGettingstarted
BuildingViews
Talking tothe Server
BuildingPresenters
GoogleWeb Toolkit
UsingData Grids
Using Data Grids
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 91
© 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 editable
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 92
© Copyright 2013, Software Alchemy
Columncolumns
1..*
DataGrid
TextColumnAlloyDataGrid...
Cellcell
1
NumberCell...
R, C CR
R is the data type of thecontents of each row in the grid.
C is the data type of thecontents of a single column.
C=NumberR, C=StringR
The DataGrid Basics
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 93
© Copyright 2013, Software Alchemy
Example Simple Grid
Header
Footer
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 94
© 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 }
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 95
© 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() { int sum = 0; for (MonitorOverviewDTO row : overviewGrid.getVisibleItems()) { sum += row.getWorkflows(); } return sum; } }; overviewGrid.addColumn(workflowsColumn, new SafeHtmlHeader(SafeHtmlUtils.fromTrustedString("Workflows")), workflowsFooter); overviewGrid.setColumnWidth(workflowsColumn, WORKFLOW_COL_SIZE, Unit.PCT); }
A footer isdefined by aHeader object.Go figure!
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 96
© 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 sorting
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 97
© Copyright 2013, Software Alchemy
Grid Sorting – Screenshot
Ascending icon
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 98
© Copyright 2013, Software Alchemy
Grid Sorting – Data Provider
public class MonitorView extends AbstractPageView {
@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); 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);
Create a data provider.
Attach grid to data provider. Attach a column sort handler.
Sort the data provider.
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 99
© 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();
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 100
© 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()); } } }
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 101
© 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(); } };
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 102
© 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); dateColumn.setCellStyleNames("columnLeft"); overviewGrid.addColumn(dateColumn, "Date"); overviewGrid.setColumnWidth(dateColumn, FIRST_COL_SIZE, Unit.PCT);
Declare column is sortable.
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 103
© 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 ListDataProvider
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 104
© Copyright 2013, Software Alchemy
Example Paged Grid
Simple paging controls.
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 105
© 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); }
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 106
© 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) { erdNovusLinksGrid.setRowData(rrdResults.getItems()); } else { // 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); } }; // connect the data provider to the grid linksDataProvider.addDataDisplay(erdNovusLinksGrid); } }
Custom data provider(see next slide)
The RPC call
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 107
© 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);}
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 108
© 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; it's not automatic
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 109
© Copyright 2013, Software Alchemy
Example Editable Grid
Page save/reset buttons
Column checkboxHighlights when changed.
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 110
© 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); if (row.isFeedingDirty()) { checkboxEl.addClassName(HtmlUtils.FIELD_CHANGED_CLASS); } else { checkboxEl.removeClassName(HtmlUtils.FIELD_CHANGED_CLASS); } evaluateSettingsStatus(); } };
Store the change
Enable/disable the Save& Reset buttons.
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 111
© Copyright 2013, Software Alchemy
Example Editable Grid
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);
Custom cell widget
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 112
© 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 slide
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 113
© 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)); } } }}
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 114
© Copyright 2013, Software Alchemy
Config
Single-PageModel
Like Java RMI
MVPRPC ...but asynchronous
ModelView
ModelPresenter
Styles / CSS
Widgets Panels
RawBinding
UI-Binding
History
Application
Form View
Search
Dashboard
Pages
DEMO #1DEMO #2
ErrorHandling
Security
SpringIntegration
CallbackAdaptor
DTOs &Transformers
ThinkingAsynchronously
Widgets &Gadgets
SimpleWidget
ComplexWidget
DialogGadgets
ExtendingGWT
Gettingstarted
BuildingViews
Talking tothe Server
BuildingPresenters
GoogleWeb Toolkit
DataGridBasics
Sorting
Paging
Edit Grids
UsingData Grids
Q & A
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 115
© Copyright 2013, Software Alchemy
User Presenter
RPC/async
DTO
reads
server calls
user actions
generates HTML View
signals events
sends data
View.ui.xml
....
..
.....
....
View.java Presenter.java
styles.css
....
..
.....
....
DTO.java 3 RPC types
LAST WORD:GWT is not Light-Weight
● As with any Web app, GWT requires a fair amount of infrastructure:
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 116
© 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 GWT's SafeHtml
5) GWT's RequestFactory for simplified (yeah right) data access
RJUG : 12-Mar-2012
Bryan Basham – The Google Web Toolkit Slide 117
© Copyright 2013, Software Alchemy
Resources
● Wikipedia
● Official GWT Home
● GWT Widget Gallery
● GWT API javadocs