COMP 405/505
Spring 2016
|
Google Web Toolkit
(GWT)
|
|
Having problems with GWT? See the
Known Issues and Problems below and/or
the GAE How-To's page.
Google Web Toolkit (GWT) is a development toolkit used to build browser-based
web applications that use GAE-based server-side processing. The
toolkit enables client-side applications to be written in Java but deployed as
Javascript. The toolkit also includes a UI designer, ala
WindowBuilder, for drag-and-drop browser-based UI creation. The
toolkit also includes debugging and performance monitoring capabilities for GWT
applications.
Note: GWT is included in the
Google Plugin for Eclipse. No
additional installations are necessary. Be sure to chose the GWT and
GAE options when installing the Google Plugin.
GWT web pages act differently than regular web pages. Instead of
progressing to a new page in response to a user action, a GWT page acts more
like a desktop application which mutates its view in response to user actions.
Key Parts of a GWT Project
All files and folders mentioned here, unless explicitly identified otherwise,
are specified relative to the root of the GWT code's package
[GWT module name].gwt.xml
- Redefines the GWT module name
- Normally, the default module name is module's root package name
- The <module rename-to='short_name'>
tag renames the module to be the 'short_name'.
- The compiled Javascript code for the client browser will be put in a
folder with the 'short_name' off the
root of the WAR folder.
- Be careful about name collisions with other entities in the WAR
folder, e.g. regular HTML or JSP files. Eclipse does
NOT check and will simply overwrite folders with a name collision.
Suggestion: use a clear naming convention for all
modules, e.g. their names all end in '_serv'.
- Defines the entry point class (fully qualtfied pathname) which is
typically [root package].client.[GWT
module name]
- Defines the folders of the client and shared code (typically,
path='client' and
path='shared')
client/[GWT module name].java
- The entry point class, extends
com.google.gwt.core.client.EntryPoint
- Has onModuleLoad() method that
instantiates and adds dynamically created HTML components to the GUI and
connects those components to their handlers.
- Can be opened with the GWT Designer in Eclipse
shared/[RPC service name].java, preferably, though in the sample code it is client/[RPC service name].java
- This interface is the same as the one used by the server-side RPC
service.
- The Eclipse sample code has this file in the
client folder, but the server code
must also implement this interface so one gets better decoupling if it is in
the shared folder instead.
- Normal method with input parameter(s) and rturn value plus
throws IllegalArgumentException
- Extends
com.google.gwt.user.client.rpc.RemoteService
- Contains the annotation
@RemoteServiceRelativePath(relative_path) where the relative path is
with respect to the GWT module's package root. The net path
should match the URL route in app.yaml.
shared/[RPC service name]Async.java, preferably, though in the sample code it is client/[RPC service name]ASync.java
- Must be in the same folder as the RPC service interface.
Recommended to be in the shared folder for
better client/server decoupling.
- The interface can be auto-generated by Eclipse by first writing the RPC
service interface and then selecting the "Quick fix" when Eclipse marks the
RPC service interface in error because the Async interface is missing.
- Instance of this interface is dynamically generated using com.google.gwt.core.client.GWT.create([RPC
service name].class) in client/[GWT
module name].java
- Method is the same as client/[RPC service
name].java except that type T
return is now void and AsyncCallback<T>
parameter added to method.
- Client uses this asynchronous proxy to make call to server side
services.
server/[RPC service name]Impl.java
- Server-side implementation of RPC service
- Implements client/[RPC service name].java
interface
- Extends RemoteServiceServlet
/war/[path]/[GWT Module name].html
- The stastic HTML web page framework into which the dynamic GWT content
is injected. Is the web page the browser actually looks at.
- In WAR folder
- May have associated CSS and other support files
- Loads client code that has been compiled into Javascript.
- <script type="text/javascript"
language="javascript" src="/[Module short name]/[Module short
name].nocache.js"></script>
- Module short name = the
redefine short name for the module specified in the
.gwt.xml file.
- Has tag ID's used by client code to identify dynamic HTML code
injection points.
- The dynamically injected code essentially replaces the tag with the
matching ID.
- <div> tags are a typical tag to set
the ID on.
/war/WEB-INF/app.yaml
- In WAR folder
- May contain URL route to /war/[path]/[GWT
Module name].html -- where the module name is
- Contains URL route to server-side Remote Procedure Call (RPC) service:
- - url: /[Module short name]/[relative_path]
servlet = [server-side implementation class] - the fully qualified classname.
- Module short name = the short name defined in the the .gwt.xml file or the full module
name if no rename defined.
- relative_path = the relative
path defined by
the @RemoteServiceRelativePath([relative_path])
annotation in the remote service interface definition.
References:
Making a new GWT web page
See the "GWT
Wizards" topic in the Google Plugin for Eclipse documentation.
- Make a new GWT Module: File/New/Other.../Google
Web Toolkit/Module
- Only need this step if not adding an entry point to an existing
module
- Creates the .gwt.xml file in the
root of the specified package
- The <source path="client">
entry is automatically created, but one must manually add another
entry for the shared folder.
- Add the following line to enable default access to the CSS
definitions corresponding to the "Clean"
visual style. Some components will not display
properly without this. These CSS files can be found under
WAR/[module short name]gwt/clean.
<inherits
name='com.google.gwt.user.theme.clean.Clean'/>
- Add the lines needed to perform logging in the GWT browser client. See the
notes on Logging.
- Make a new GWT Entry Point:
File/New/Other.../Google Web Toolkit/Entry Point Class
- The package should be the "client"
package below your module package root.
- Add some startup code to the
onModuleLoad() method of the newly created EntryPoint class so
that the GWT Designer will work properly (see
below). This code may be changed later; this is just to get
the GWT Designer started. In
onModuleLoad():
- Create a private field and instantiate a
com.google.gwt.user.client.ui.DockPanel -- this type of
panel uses a BorderLayout-like
layout which is convenient for top-level containers.
- Make onModuleLoad() delegate to
a private void initGUI() method.
- In initGUI(), add the panel to the RootPanel, using a reasonable name for the
location of the top-level container, e.g.
"body_div" here which refers to the ID of a
div tag in the
body of the HTML web page.
RootPanel.get("body_div").add(myDockPanel);
- Run a GWT Compile to create the folders in the WAR folder that hold
the compiled Javascript code.
- Highlight the Eclipse project -- GWT compile will NOT work
correctly if anything but the entire project is selected.
- Either pull down the Google toolbar icon or right click
the project and open up "Google".
- Select "GWT Compile" and click
"Compile". Be sure that
a "GWT Compile" dialog box comes
up! If not, you are probably not selecting the
entire project.
- If you have created a new GWT module, you must manually add
it to the list of "Entry Point Modules"
in the GWT Compile dialog
window.
- Make a new GWT HTML page:
File/New/Other.../Google Web Toolkit/HTML Page
- May not properly connect to the generated Javascript.
There should be a script tag that
references a file called something like this:: /[pathname]/[module
name].nocache.js If not, locate this file in the
WAR folder heirarchey and set the script
tag to reference it.
- Add a place to put the dynamically generated widgets.
As per the previous example, in the <body>
of the web page, an empty div tag with
the id set to the name used above is
convenient: <div
id="body_div"></div>
- Add a URL route to the new HTML page in
app.yaml, if necessary. This is only necessary if you
want to page to be accessed as something other than it's full pathname
from the root of the WAR folder. Note that if the filename,
e.g. index.html is included in the
welcome_files directive in
app.yaml, then the page can be accessed by only using its folder
name.
GWT Designer
Be sure that you have the
GWT Designer configured properly
before you start!
The GWT Designer can be used to edit EntryPoint classes in much the same way
as WindowBuilder. There are a few caveats to remember however:
- Make sure you are using widgets from
com.google.gwt.user.client.ui, not the usual
javax.swing,
java.awt, etc.!
- There must be at least one container widget, e.g. a
DropPanel, instantiated in
onModuleLoad() or the Designer will not be
able to add more widgets.
- Only widgets that have either themselves directly or indirectly via
their containing widgets, been added to the
RootPanel will show up in the Designer.
- The code for adding a widget to the RootPanel must be added manually,
not through the Designer: RootPanel.get("anID").add(aWidget);
- It is possible to add widgets to different locations in the
RootPanel's HTML page; simply add the widget using a different ID
(make sure the ID exists in the HTML page).
- Changes made in the Designer will NOT show up in the running application
until a GWT Compile has been performed!
- The Development Mode Server does not generally need to be restarted
when a GWT Compile is performed.
Server-side processing (Remote Procedure Calls)
The Java code in a GWT module is cross-compiled into client-side Javascript.
This makes it easy to off-load a fair amount of GUI processing onto the client.
But to make a server-sdie call, one can use GWT's Remote Procedure Call (RPC)
mechanism to make what is really a servlet call into what seems to be a simple
method call on an client-side object. This is conceptually the same
as what Java RMI does, though a little different because a normally blocking,
synchronous call must be translated into a non-blocking, asynchronous call.
To create an RPC call from a GWT client to a servlet backend, several pieces
are needed:
- In the module's "shared" folder (be
sure the shared folder is listed as a "source"
in the module's .gwt.xml file!):
- The interface that defines the server to the client. i.e. defines
what RPC calls can be made.
- An "Async" proxy of the above
interface. This can be auto-generated from the above
interface.
- In the module's "server" folder (the
shared folder is NOT listed in the
module's .gwt.xml file because it's
contents are not cross-compiled into Javascript):
- See the requirements for
server/[RPC service name]Impl.java above.
- This implements the RPC service interface above (the synchronous
original, not the asynchronous proxy).
- In the module's EntryPoint class
- See the requirements for
client/[GWT module name].java above
- Contains the field or variable of the sort
MyServiceAsync myService = GWT.create(MyService.class)
- All calls are made through the asynchronous proxy,
myService using
AsyncCallback<ReturnType>
lambdas (success/fail visitors actually).
- In the app.yaml configuration
- See the requirements for the
app.yaml file above.
- The URL pattern to the server implementation is the combination of
the specifications in the module "rename-to"
in the module's .gwt.xml file
(the "short name") and the relative_path defined in the
RPC service interface.
Be careful when refactoring that you don't break the relationships
between the above 5 entities!
Returning Objects from the Server to the Client
It is critical to always remember that a GWT server is a Java program but a
GWT client is a Javascript program. Thus, any interaction between
them is fundamentally a cross-language process as well as a cross-machine one.
This has several specific consequences:
- No returning of anonymous inner classes -- the GWT
compiler will complain about not finding a specific class to serialize.
Plus, the closure issues are just going to cause trouble.
- Any returned objects must implement
com.google.gwt.user.client.rpc.IsSerializable -- this
is the GWT equivalent of java.io.Serializable.
- Any returned object type must have a public no-parameter
constructor -- This is because the deserializer will first call the
no-parameter constructor to create an instance of the object and then
proceed to set the field values. Having an additional,
parameterized constructor is fine for use by the server to easily
instantiate the object.
- Access to contained data should be via getters and setters
-- encapsulate the data. Read-only data simply means a getter
but no setter for the affected value(s).
The above restrictions are essentially the description of a Java Bean..
Because any returned data types are shared between the client and server, the
code should be in the "shared" folder of the
GWT module.
References:
System Design with GWT
Reusing common code
If multiple modules use the same operations or data or GUI elements,
then one should refactor that code out into a separate module. Note
that modules are not required to have an entry point, which is just the UI portion
of the module. This is particularly useful for defining common data
classes that are used for information transport between the front and backends.
To use the common code in another modules, do the following:
- "Inherit" the common module by adding the following line to the
.gwt.xml file of the module using it:
<inherits name="[common module root
package].[common module name]">, e.g.
<inherits name="edu.rice.comp405.common.Common"/>
- If the utilizing module is using common RPC server functionalities, in
the utilizing module's EntryPoint class,
create an Async proxy for the common RPC
server implementation.
- This additional proxy can be used as normal.
- Create a hybrid URL route in the app.yaml since the common code is
actually imbedded in the untilizing module's Javascript. The URL
route should look like:
- url: utilizing_module_short_name/common_module_relative_path # e.g. user_serv/common_service
servlet: common_server_classname # e.g. edu.rice.comp405.common.server.CommonServerImpl
name: unique_name # e.g. userCommonServerImpl
All 3 steps above must be repeated every
time the common module is used in another utilizing module.
References:
Custom GWT Widgets
When creating a complex GUI with multiple, non-trivial panels of controls or
when you want the same generic set of controls to be used in multiple places in
your system, you should avoid duplicating code by creating a custom GWT widget.
To make a custom GWT widget, you need to use the rather non-obviously named
GWT "UiBinder" wizard under "New/Other.../Google
Web Toolkit/UiBinder". Give the new widget class an
appropiate name and make sure that for "Create UI
based on:" is set to "GWT widgets".
Deselect the "Generate sample content" because
the sample code is more confusing than useful.
Custom GWT widgets are slightly different than GWT module
EntryPoint classes. Two
files will be generated:
- [Widget Classname].java --
this is where the Java code to run the widget is located, e.g. event
handlers, constructor, etc. GWT Designer will add some
annotations that are not present in a regular
EntryPoint class.
- [Widget Classname].ui.xml --
this is the GUI controls layout file and thus, what the GWT Designer can
open to do graphical editing of the Widget. The GWT
Designer will automatically put any initialization and event code into the
Java file.
The Java code that you normally see in the "Source"
tab in the GWT Designer is in the associated Java file, not the renamed "XML
Source" tab. The graphical layout editor is still on the "Design"
tab.
You can add public or package private methods to the widget's Java code to
enable the user of the widget to set values and otherwise interact with it.
Adding events can be a bit tricky however.
Attaching events to custom GWT widgets
When you use the GWT Designer to create an event handler for a widget, it
will create the event handler in the Java code as a method rather than as an
event handler installed into a widget. The problem is that the
custom widget is an encapsulated entity with regards to theparts of the system
that use the widget. You need event handlers that can communicate
both with the rest of your system and the internal pieces of the widget.
There are two approaches to this problem:
- Make a non-private method of your widget that will install an
externally supplied event handler into the desired GUI control.
- Big drawback: Because the event handler is
outside of the widget's encapsulation
it cannot interact with any of the widget's other GUI controls.
For instance, suppose your custom widget has a text field and a button.
You cannot install an event handler for the button that will retrieve
the text from the text field because the event handler cannot access the
private text field. You could create non-private
methods that break the widget's encapsulation and expose the desired
controls, but that solution is problematic. This
technique is thus only really useful if the event handler does not need
to interact with the rest of the custom widget.
- Delegate to an overridden method
- In the event handler auto-generated by the GWT Designer, delegate to
a non-private, abstract method of the widget's class.
- The input parameters of this new method should be the control(s)
and/or data that the the event needs to access. The
event handler code should supply the references to the control(s)
and/or data when it makes the delegating call. In
general, passing data rather than control references will protect
the custom widget's encapsulation better. For instance,
pass a text field's text rather than a reference to the text field
itself if all you want to do is to read the text field.
If the event code needs to mutate the control you can eitehr pass
the reference to the control or add protected methods to the widget
to mutate the control.
- In the code that uses the custom widget, simply use an anonymous
inner class to override the abstract method and supply the
usage-specific code that is needed.
Known Issues and Problems
GWT Designer very slow to load
The first time it is invoked, the GWT Designer can take over a minute to
load. It will also eat up a large fraction of your system
resources. Subsequent loads shold be faster. Increasing
the amount of memory available to Eclipse may help, see below.
"This is not a GUI
class and can't be edited graphically" Error
This error will arise if you try to edit an
EntryPoint class with a defined but empty
onModuleLoad() method, e.g. a newly
created file. For some reason, the GWT Designer wants at least one
GUI widget to be defined in the onModuleLoad() method before it
can perform graphical editing. Follow the
directions above for creating some startup code for the GWT Designer to work
with.
References:
Out of Memory Error
If Eclpse crashes with an "out of memory error", try the following
configuration changes:
- Start your favorite text editor (Windows: start "As
Administrator")
- In the root of the Eclipse EE installation folder, open
eclipse.ini
- Find the -Xms and
-Xmx parameters, probably at the end of the file
(the exact values may be different):
-Xms40m
-Xmx512m
- Try doubling these values and see if GWT still crashes.
- If it still crashes, add the -XX:PermSize
and -XMMaxPermSize values right after the
-Xms and
-Xmx declarations:
-Xms80m
-Xmx1024m
-XX:PErsmSize=512m
-XX:MaxPermSize=1024m
- Adjust the parameter values until the "Out of memory" error goes away.
Google recommends the following settings:
-vmargs
-XX:MaxPermSize=128m
-Xms256m
-Xmx512m
References:
SVN Errors
The Development Mode Server must be stopped when doing a
commit to SVN because the development server has a hold of some temporary files
that SVN will fail to commit.
One can set svn:ignore
for the gwt-unitCache folder since it is just
files to help the GWT compiler remember what has been compiled.
Another option is to mark the
gwt-unitCache folder as "Derived" in the
folder's Properties.
Removing gwt-unitCache
from SVN
The best thing seems to be to set the
svn:ignore before the gwt-unitCache is ever
committed for the first time to SVN. But, if you have already
committed the cache to the repository at least once already, follow these steps
to get SVN to ignore it:
- Commit everything as usual.
- In Eclipse, delete the entire
gwt-unitCache directory.
- Commit the entire project again.
- Right-click the project root and select
Team/Set Property
- Property name =
svn:ignore
- Text property =
gwt-unitCache
- Save everything and commit again.
If you now do a GWT compile, the gwt-unitCache folder
will automatically be recreated. but it should now show up in Eclipse's
Project Explorer with no adornments on its icon indicating any SVN status.
The cached files will not be committed to the repository anymore.
References:
© 2015 by Stephen Wong