December 10, 2007
Hands-On Google Web ToolkitDesigning the User Interface
The sample application's UI consists of two widgets (UI elements) and a main controller. The first widget, AlbumWidget, lets users select an image service, input their username, and select a photo album; see Figures 1 and 2. The second widget, ImageFlowWidget, displays the photos and provides navigation through the album. The main program (GWTFlow.java) controls the two widgets and maintains application state. All of the widgets are dynamically added and removed from the placeholder <div> element we put in the host HTML page.
AlbumWidget is constructed using a number of simpler GWT-provided widgets, such as Button, Image, Label, RadioButton, and TextBox. These are all arranged on VerticalPanels and HorizontalPanels. (For documentation on these and more, see the references at the end of this article.)
The advantage of using the widgets and panels provided by the toolkit is that they work the same way on all browsers. A prime example is the scrollable selector for choosing the photo in Figure 2. This would be difficult to implement in traditional HTML; but in GWT, it's a simple Grid table contained within a ScrollPanel. That's not to say all cross-browser problems go awaywe experienced a few quirks with Internet Explorer and Safaribut they are significantly fewer when compared to traditional Ajax development.
[Click image to view at full size]
Figure 1: Users can select a photo service and username.
[Click image to view at full size]
Figure 2: List of albums is retrieved from the server and displayed in a scrollable grid.
ImageFlowWidget extends AbsolutePanel, which allows for absolute positioning and overlapping of widgets. Image widgets are placed in this panel and scaled to simulate perspective. The main image is always the largest and is positioned in the center of the window. Other pictures are stacked underneath and sized smaller and smaller the further they are from the center.
When browsers display absolute positioned elements, they overlap elements that were added later on top of earlier ones. So we need to add images from the outside in (using AbsolutePanel.add()), with the main image last. Image positions, however, are calculated from the inside out to prevent smaller images from getting completely hidden. Listing Three presents the algorithm for handling this. Starting from the center image, it first calculates the size and position, recurses to the neighboring image, calls AbsolutePanel.add(), and finally returns to the caller.
Listing Three
Animation
GWT does not currently provide a built-in animation or effects library (although developers are working on one for an upcoming release). Instead, you need to call out to a JavaScript library such as Script.aculo.us (script.aculo.us), YUI (developer.yahoo.com/yui), or Rico (openrico.org). For GWTFlow, we chose the Effect class in the open-source GWT Widget Library. Using GWT's JavaScript Native Interface (JSNI), it wraps the Script.aculo.us effects library, providing animation for moving, scaling, and fade effects.
When users navigate to a new image, GWTFlow recalculates the new images and sizes, calls Effect.move() and Effect.scale() (provided by the GWT Widget Library), and lets Script.aculo.us work its magic. Listing Four presents the method called to animate the image movement.
private void animateMove(Widget widget, int left, int top, double dur)
{
Effect.move(widget, new EffectOption[] { new EffectOption("mode", "absolute"),
new EffectOption("x", left), new EffectOption("y", top),
new EffectOption("duration", dur)} );
}
Listing Four
Getting Data From Flickr
When users type in a username and click the Next button, we need to retrieve the list of images and albums from the server. Flickr provides access to its data via an extensive web service API (www.flickr.com/services/api). We created a proxy service to provide this data using GWT's Remote Procedure Call (RPC) functionality (Figure 3).
[Click image to view at full size]
Figure 3: ImageService acts as a proxy between the client and Flickr.
RPC services in GWT extend from the Java Servlet API, with built-in callbacks defined to make everything asynchronous. The objects returned are serializable POJOs (Plain Old Java Objects), which we use to store image and album metadata.
The actual calls to Flickr are done in ImageService.java using Flickrj, an open-source Java library that wraps Flickr's REST-based API (flickrj.sourceforge.net). Listing Five shows the code to look up users, get a list of their photo sets, and transform the list into an array of lightweight Album data transfer objects.
public AlbumDTO[] getFlickrAlbums(String username) throws IOException,
SAXException, FlickrException
{
Flickr flickr = FlickrUtil.getInstance(); // Access Flickr using API key
PeopleInterface people = flickr.getPeopleInterface();
User user = people.findByUsername(username);
PhotosetsInterface photoSets = flickr.getPhotosetsInterface();
Collection sets = photoSets.getList(user.getId()).getPhotosets();
AlbumDTO[] albums = new AlbumDTO[sets.size()];
Iterator iter = sets.iterator();
for (int i = 0; i < albums.length; i++) {
Photoset set = (Photoset) iter.next();
albums[i] = new AlbumDTO(); // Custom POJO w/Album metadata
albums[i].id = set.getId();
albums[i].name = set.getTitle();
albums[i].description = set.getDescription();
albums[i].imageCount = set.getPhotoCount();
albums[i].smallSquareUrl = set.getPrimaryPhoto().getSmallSquareUrl();
}
return albums;
}
Listing Five
As in any Ajax application, client-side responsiveness is a primary concern. An RPC service lets us minimize the data sent across the wire and use the server's processing power to retrieve image information. It also allows for a cleanly defined API that can be extended to support other image providers, such as Google's Picasa Web Albums.
However, RPC services typically require a servlet engine such as Tomcat or JBoss, adding complexity to deployment and limiting the number of hosting providers. One alternative we considered was JSON (JavaScript Object Notation), a data interchange format with support for native JavaScript web service requests. JSON data feeds are supported by a variety of providers, including Flickr and some Google services (although not Picasa). In the end, we decided against JSON, favoring RPC for performance, ease of debugging, and code maintainability.
|
|
||||||||||||||||||||||||||||||
|
|
|
|