USER INTERFACE

Building User Interfaces

As shown in the gallery, GWT includes a variety of pre-built Java widgets and panels that serve as cross-browser building blocks for your application. GWT also includes unique and powerful optimization facilities such as image bundles.

Contents

  • Overview
    As shown in the gallery, GWT includes a variety of pre-built Java widgets and panels that serve as cross-browser building blocks for your application. GWT also includes unique and powerful optimization facilities such as image bundles.
  • Widgets and Panels
    Widgets and panels are client-side Java classes used to build user interfaces.
  • Widgets Gallery
    A gallery of widgets and panels.
  • Events and Listeners
    Widgets publish events using the well-known listener pattern.
  • Creating Custom Widgets
    Create your own widgets completely in Java code.
  • Understanding Layout
    Understanding how widgets are laid out within panels.
  • Style Sheets
    Widgets are most easily styled using cascading style sheets (CSS).
  • Image Bundles
    Optimize the performance of your application by reducing the number of HTTP requests for images.
    • Creating and Using an Image Bundle
      Define an image bundle and use it in your application.
    • Image Bundles and Localization
      Create locale-sensitive image bundles by using GWT's localization capabilities.

Overview

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.

While it is possible to manipulate the browser's DOM directly using the DOM interface, it is far easier to use classes from the Widget hierarchy. You should rarely, if ever, need to access the DOM directly. Using widgets makes it much easier to quickly build interfaces that will work correctly on all browsers.

Widgets and Panels

GWT applications construct user interfaces using widgets that are contained within panels. Examples of widgets include Button, TextBox, and Tree.

Widgets and panels work the same way on all browsers; by using them, you eliminate the need to write specialized code for each browser. But you are not limited to the set of widgets provided by the toolkit. There are a number of ways to create custom widgets yourself.

Panels

Panels, such as DockPanel, HorizontalPanel, and RootPanel, contain widgets and are used to define how they are laid out in the browser.

Styles

Visual styles are applied to widgets using Cascading Style Sheets (CSS). This section describes in detail how to use this feature.

Widgets Gallery

The following are widgets and panels available in the GWT user-interface library.

Events and Listeners

Events in GWT use the "listener interface" model similar to other user interface frameworks. A listener 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 listener 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 listener interface is ClickListener.

public void anonClickListenerExample() {

Button b = new Button("Click Me");
b.addClickListener(new ClickListener() {
public void onClick(Widget sender) {
// handle the click event
}
});
}
Using anonymous inner classes as in the above example can be inefficient for a large number of widgets, since it could result in the creation of many listener objects. Widgets supply their this pointer as the sender parameter when they invoke a listener method, allowing a single listener to distinguish between multiple event publishers. This makes better use of memory but requires slightly more code, as shown in the following example:
public class ListenerExample extends Composite implements ClickListener {

private FlowPanel fp = new FlowPanel();
private Button b1 = new Button("Button 1");
private Button b2 = new Button("Button 2");

public ListenerExample() {
initWidget(fp);
fp.add(b1);
fp.add(b2);
b1.addClickListener(this);
b2.addClickListener(this);
}

public void onClick(Widget sender) {
if (sender == b1) {
// handle b1 being clicked
} else if (sender == b2) {
// handle b2 being clicked
}
}
}
Some event interfaces specify more than one event. If you are only interested in a subset of these events, subclass one of the event "adapters". Adapters are simply empty concrete implementations of a particular event interface, from which you can derive a listener class without having to implement every method.
public void adapterExample() {

TextBox t = new TextBox();
t.addKeyboardListener(new KeyboardListenerAdapter() {
public void onKeyPress(Widget sender, char keyCode, int modifiers) {
// handle only this one event
}
});
}

Creating Custom Widgets

GWT makes it easy to create custom widgets entirely in the Java language.

Composites

Composites are by far the most effective way to create new widgets. You can easily combine groups of existing widgets into a composite that is itself a reusable widget. Composite is a specialized widget that can contain another component (typically, a panel) but behaves as if it were its contained widget. Using Composite is preferable to attempting to create complex widgets by subclassing Panel because a composite usually wants to control which methods are publicly accessible without exposing those methods that it would inherit from its panel superclass. This is an example of how to create a composite.

From Scratch in Java code

It is also possible to create a widget from scratch, although it is trickier since you have to write code at a lower level. Many of the basic widgets are written this way, such as Button and TextBox. Please refer to the implementations of these widgets to understand how to create your own.

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 be done only 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 its underlying implementation.

Understanding Layout

Panels in GWT are much like their counterparts in other user interface libraries. The main difference lies in the fact that they use HTML elements such as DIV and TABLE to layout their child widgets.

RootPanel

The first panel you're likely to encounter is the RootPanel. This panel is always at the top of the containment hierarchy. The default RootPanel wraps the HTML document's body, and is obtained by calling RootPanel.get(). If you need to get a root panel wrapping another element in the HTML document, you can do so using RootPanel.get(String).

CellPanel

CellPanel is the abstract base class for DockPanel, HorizontalPanel, and VerticalPanel. What these panels all have in common is that they position their child widgets within logical "cells". Thus, a child widget can be aligned within the cell that contains it, using setCellHorizontalAlignment() and setCellVerticalAlignment(). CellPanels also allow you to set the size of the cells themselves (relative to the panel as a whole) using CellPanel.setCellWidth and CellPanel.setCellHeight.

Other Panels

Other panels include DeckPanel, TabPanel, FlowPanel, HTMLPanel, and StackPanel.

Sizes and Measures

It is possible to set the size of a widget explicitly using setWidth(), setHeight(), and setSize(). The arguments to these methods are strings, rather than integers, because they accept any valid CSS measurements, such as pixels (128px), centimeters (3cm), and percentage (100%).

Style Sheets

GWT widgets rely on cascading style sheets (CSS) for visual styling. Each widget has an associated style name that binds it to a CSS rule. A widget's style name is set using setStyleName(). For example, the Button has a default style of gwt-Button. In order to give all buttons a larger font, you could put the following rule in your application's CSS file:
.gwt-Button { font-size: 150%; }

Complex Styles

Some widgets have somewhat more complex styles associated with them. MenuBar, for example, has the following styles:

   .gwt-MenuBar { the menu bar itself }

.gwt-MenuBar .gwt-MenuItem { menu items }
.gwt-MenuBar .gwt-MenuItem-selected { selected menu items }

In this example, there are two styles 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.

CSS Files

Typically, stylesheets are placed in a package that is part of your module's public path. Then simply include a reference to the stylesheet in your host page, such as

Documentation

It is standard practice to document the relevant CSS style names for each widget class as part of its doc comment. For a simple example, see Button. For a more complex example, see MenuBar.

Image Bundles

Typically, an application uses many small images for icons. An HTTP request has to be sent to the server for each of these images, and in some cases, the size of the image is smaller than the HTTP response header that is sent back with the image data. These round trips to the server for small pieces of data are wasteful. Even when the images have been cached by the client, a 304 ("Not Modified") request is still sent to check and see if the image has changed. Since images change infrequently, these freshness checks are also wasteful.

Sending out requests and freshness checks for many images will slow down your application. HTTP 1.1 requires browsers to limit the number of outgoing HTTP connections to two per domain/port. A multitude of image requests will tie up the browser's available connections, which blocks the application's RPC requests. RPC requests are the real work that the application needs to do.

To solve this problem, GWT introduces the concept of an image bundle. An image bundle 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 an image bundle 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.

Since 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.

Tip
Check out the ImageBundle 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

Creating and Using an Image Bundle

To define an image bundle, the user needs to extend the ImageBundle interface. The ImageBundle interface is a tag interface that can be extended to define new image bundles.

The derived interface can have zero or more methods, where each method

  • takes no parameters,
  • has a return type of AbstractImagePrototype, and
  • may have an optional gwt.resource metadata tag which specifies the name of the image file in the module's classpath

Valid image file types are png, gif, and jpg. If the image name contains '/' characters, it is assumed to be the name of a resource on the classpath, formatted as would be expected by ClassLoader.getResource(String). Otherwise, the image must be located in the same package as the user-defined image bundle.

If the gwt.resource metadata tag is not specified, then

  • the image filename is assumed to match the method name,
  • the extension is assumed to be either .png, .gif, or .jpg, and
  • the file is assumed to be in the same package as the derived interface

In the event that there are multiple image files with different extensions, the order of extension precedence is (1) png, (2) gif, then (3) jpg.

An image bundle for icons in a word processor application could be defined as follows:

public interface WordProcessorImageBundle extends ImageBundle {


/**
* Would match the file 'new_file_icon.png', 'new_file_icon.gif', or
* 'new_file_icon.png' located in the same package as this type.
*/
public AbstractImagePrototype new_file_icon();

/**
* Would match the file 'open_file_icon.gif' located in the same
* package as this type.
*
* @gwt.resource open_file_icon.gif
*/
public AbstractImagePrototype openFileIcon();

/**
* Would match the file 'savefile.gif' located in the package
* 'com.mycompany.mygwtapp.icons', provided that this package is part
* of the module's classpath.
*
* @gwt.resource com/mycompany/mygwtapp/icons/savefile.gif
*/
public AbstractImagePrototype saveFileIcon();
}

Methods in an image bundle return AbstractImagePrototype objects (rather than Image objects, as you might have expected) because AbstractImagePrototype objects provide additional lightweight representations of an image. For example, the AbstractImagePrototype.getHTML() method provides an HTML fragment representing an image without having to create an actual instance of the Image widget. In some cases, it can be more efficient to manage images using these HTML fragments.

Another use of AbstractImagePrototype is to use AbstractImagePrototype.applyTo(Image) to transform an existing Image into one that matches the prototype without having to instantiate another Image object. This can be useful if your application has an image that needs to be swapped depending on some user-initiated action. Of course, if an Image is exactly what you need, the AbstractImagePrototype.createImage() method can be used to generate new Image instances.

The following example shows how to use the image bundle that we just defined in your application:

public void useImageBundle() {

WordProcessorImageBundle wpImageBundle = (WordProcessorImageBundle) GWT.create(WordProcessorImageBundle.class);
HorizontalPanel tbPanel = new HorizontalPanel();
tbPanel.add(wpImageBundle.new_file_icon().createImage());
tbPanel.add(wpImageBundle.openFileIcon().createImage());
tbPanel.add(wpImageBundle.saveFileIcon().createImage());
}
Tip
Image bundles are immutable, so you can keep a reference to a singleton instance of an image bundle instead of creating a new instance every time the image bundle is needed.

Image Bundles and Localization

Sometimes applications need different images depending on the locale that the user is in. When using image bundles, this means that we need different image bundles for different locales. Although image bundles and localization are orthogonal concepts, they can work together by having locale-specific factories create instances of image bundles.

The best way to explain this technique is with an example. Suppose that we define the following ImageBundle for use by a mail application:

public interface MailImageBundle extends ImageBundle {


/**
* The default 'Compose New Message' icon if no locale-specific
* image is specified.
*
* @gwt.resource compose_new_message_icon.gif
*/
public AbstractImagePrototype composeNewMessageIcon();

/**
* The default 'Help' icon if no locale-specific image is specified.
* Will match 'help_icon.png', 'help_icon.gif', or 'help_icon.jpg' in
* the same package as this type.
*/
public AbstractImagePrototype help_icon();
}
Suppose the application has to handle both English and French users. We define English and French variations of each image in MailImageBundle by creating locale-specific image bundles that extend MailImageBundle:
public interface MailImageBundle_en extends MailImageBundle {


/**
* The English version of the 'Compose New Message' icon.
* Since we are not overriding the help_icon() method, this bundle
* uses the inherited method from MailImageBundle.
*
* @gwt.resource compose_new_message_icon_en.gif
*/
public AbstractImagePrototype composeNewMessageIcon();
}
public interface MailImageBundle_fr extends MailImageBundle {


/**
* The French version of the 'Compose New Message' icon.
*
* @gwt.resource compose_new_message_icon_fr.gif
*/
public AbstractImagePrototype composeNewMessageIcon();

/**
* The French version of the 'Help' icon.
*
* @gwt.resource help_icon_fr.gif
*/
public AbstractImagePrototype help_icon();
}
The final step is to create a mechanism for choosing the correct image bundle based on the user's locale. By extending Localizable, we can create a locale-sensitive factory that will return new instances of MailImageBundle that match the factory's locale:
public interface MailImageBundleFactory extends Localizable {


public MailImageBundle createImageBundle();
}
public class MailImageBundleFactory_en implements MailImageBundleFactory {


public MailImageBundle createImageBundle() {
return (MailImageBundle) GWT.create(MailImageBundle_en.class);
}
}
public class MailImageBundleFactory_fr implements MailImageBundleFactory {


public MailImageBundle createImageBundle() {
return (MailImageBundle) GWT.create(MailImageBundle_fr.class);
}
}
The application code that utilizes a locale-sensitive image bundle would look something like this:
public void useLocalizedImageBundle() {

// Create a locale-sensitive MailImageBundleFactory
MailImageBundleFactory mailImageBundleFactory = (MailImageBundleFactory) GWT
.create(MailImageBundleFactory.class);

// This will return a locale-sensitive MailImageBundle, since we are using
// a locale-sensitive factory to create it.
MailImageBundle mailImageBundle = mailImageBundleFactory.createImageBundle();

// Get the image prototype for the icon that we are interested in.
AbstractImagePrototype helpIconProto = mailImageBundle.help_icon();

// Create an Image object from the prototype and add it to a panel.
HorizontalPanel panel = new HorizontalPanel();
panel.add(helpIconProto.createImage());
}

0 comments: