GWT fu, Part 1: Going places with Google Web Toolkit

Implement desktop-like Web applications with Java code

Google Web Toolkit (GWT) lets you use the Java™ language to implement rich client user interfaces that run in a browser. In this two-part article, David Geary brings you up to speed on the latest version of GWT and shows you how to implement a desktop-like Web application.

David Geary, President, Clarity Training, Inc.

David GearyAuthor, speaker, and consultant David Geary is the president of Clarity Training, Inc., where he teaches developers to implement Web applications using GWT and JavaServer Faces (JSF). He was on the JSTL 1.0 and JSF 1.0/2.0 Expert Groups, co-authored Sun's Web Developer Certification Exam, and has contributed to open source projects, including Apache Struts and Apache Shale. David's Graphic Java Swing was one of the best-selling Java books of all time, and Core JSF (co-written with Cay Horstman), is the best-selling JSF book. David is also the author of GWT Solutions, and he speaks regularly at conferences and user groups. He has been a regular on the NFJS tour since 2003, has taught courses at Java University, and is a three-time JavaOne rock star.



01 September 2009

Also available in Chinese Japanese

In the 1990s, I used Swing a fair bit. I loved Swing because it let me implement anything I could imagine. To me, that's the thrill of developing software: being able to imagine something, and then make it come to life onscreen. With the Swing API, anything from drag-and-drop to arcade games was within easy reach.

Then along came server-side Java and primitive frameworks like Struts, which made it possible to — what?! — return to the 1960s and implement mainframe-like forms. No drag-and-drop, no arcade games, and not much fun anymore, once we were thrown back to the stone age of programming.

That's why I love Google Web Toolkit. By using a Swing-like API, you can once again implement anything you can imagine, in a browser. Sure, the state of Web application frameworks has changed considerably since Struts 1.0, and it's now possible to implement much more than mainframe-like forms with frameworks like JSF 2, Ruby on Rails, and Lift. But GWT is unrivaled at giving you access to the raw firepower of JavaScript through a familiar language and a familiar API. If you want to implement a desktop-like application that runs in the browser, then GWT is a serious contender for your business, at least on the client side.

This two-part article reviews some GWT basics, updates you on relevant changes in the current GWT API, and gives you a look at advanced aspects of GWT development. For a comprehensive introduction to GWT's initial release, read "Ajax for Java developers: Exploring the Google Web Toolkit."

In this short series, I'll walk you up the GWT learning curve by implementing a desktop-like application. In this article, I cover:

  • Widgets
  • Remote procedure calls (RPCs) and database integration
  • Composite widgets
  • Event handlers
  • Ajax testing

In Part 2, I'll take a closer look at implementing custom widgets and at some advanced techniques such as using event previews and animating images with timers. You can download the source code for the complete sample application.

Places: An Ajaxified, database-backed, Web services mashup

The places application that I'll build with GWT lets you view places. I define a place as a combination of map and weather information for a given location, as you can see in Figure 1:

Figure 1. The places application: Viewing a single place
The places application: Viewing a single place

The places application comes with six built-in addresses, which the application fetches from a MySQL database at startup. The application then displays the addresses in a list box. When you click on an item in the list box, the application updates the grid to the right of the list box with the selected address.

Compare GWT and JavaServer Faces (JSF)

This article's places application is similar to an application of the same name that I discussed in the three-part JSF 2 fu series. You can read both series to get a feel for how the two frameworks differ.

When you click the grid's Show button, the application opens a window. The window contains a map on the left and weather information on the right, separated by a horizontal split panel. The application obtains the map and weather information from Yahoo! Web Services. You can display multiple windows at once and adjust the split panel's dimensions, as shown in Figure 2:

Figure 2. The places application: Viewing multiple places
The places application: Viewing multiple places

There's one thing you can't see in the static screenshots in Figure 1 and Figure 2: Maps reside in viewports, so you can drag the map around in the split panel. If you drag the map quickly — for less than one second — the viewport animates the view by continually moving the view in the direction you dragged the mouse. When the image gets to an edge of the viewport, it bounces off the edge and continues to animate until you click the mouse in the viewport. Download the application if you want to try this out.

I will discuss the viewport, which illustrates some advanced GWT techniques such as using timers for animations and using event previews, in Part 2.


Widgets

For the rest of the article, I'll implement the places application from the ground up. First, I fetch addresses from a database and show them in a list box, as shown in Figure 3:

Figure 3. Widgets and database access
Widgets and database access

Listing 1 shows the code for the application displayed in Figure 3:

Listing 1. Places.java, take 1
package com.clarity.client;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.HorizontalSplitPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.RootPanel;

public class Places implements EntryPoint {
  final ListBox addresses = new ListBox();
  final ArrayList<Address> addressList = new ArrayList<Address>();
  final HorizontalSplitPanel hsp = new HorizontalSplitPanel();

  public void onModuleLoad() {
    hsp.add(addresses);
    hsp.add(new Label("Address grid goes here"));
    hsp.setSplitPosition("175px");
    
    getAddresses();
    
    RootPanel.get().add(hsp);
  }

  public void getAddresses() {
    // Instantiate the address service
    AddressServiceAsync as = (AddressServiceAsync) GWT
        .create(AddressService.class);

   // Use the address service to fetch addresses and populate the listbox
    ...
  }  
}

All GWT applications are GWT modules, and all GWT modules implement the EntryPoint interface, which in turn defines one method: public void onModuleLoad(). That method is analogous to a main() method in a desktop application. For the places application, the onModuleLoad() method adds the address list box and a label to a horizontal split panel. Then it fetches addresses from the database and adds the split panel to the application's root panel. (The root panel represents the body of the application's HTML page.)

Creating widgets and configuring them is easy with GWT. Fetching and displaying values from a database is a bit more complicated.


RPCs and database integration

To fetch addresses from the database, I use a GWT RPC. First, I instantiate an instance of AddressServiceAsync with the GWT.create() method, and then I use that instance to invoke the RPC and capture the result of the call, as shown in Listing 2:

Listing 2. Places.java, take 2
package com.clarity.client;

public class Places implements EntryPoint {
  final ListBox addresses = new ListBox();
  final final ArrayList<Address> addressList = new ArrayList<Address>();
  final HorizontalSplitPanel hsp = new HorizontalSplitPanel();

  public void onModuleLoad() {
    hsp.add(addresses);
    hsp.add(new Label("Address grid goes here"));
    hsp.setSplitPosition("175px");
    
    getAddresses();
    
    RootPanel.get().add(hsp);
  }

  public void getAddresses() {
    // Instantiate the address service
    AddressServiceAsync as = (AddressServiceAsync) GWT
        .create(AddressService.class);

    as.getAddresses(new AsyncCallback<List<Address>>() {
      public void onFailure(Throwable caught) {
        GWT.log("Can't access database", caught);
      }

      public void onSuccess(List<Address> result) {
        Iterator<Address> it = result.iterator();
        
        while (it.hasNext()) {
          Address address = it.next();
          addresses.addItem(address.getAddress());
          addressList.add(address);
        }
        addresses.setVisibleItemCount(result.size());
      }
    });  
}

GWT RPCs are defined by two interfaces: an asynchronous interface that you invoke on the client, and a remote interface that GWT invokes on the server. For the address service, those interfaces are AddressServiceAsync and AddressService, respectively. I call GWT.create in Listing 2, passing it the class of the remote interface, and GWT returns an instance of the asynchronous interface.

Listing 3 shows the AddressService interface:

Listing 3. AddressService.java
package com.clarity.client;

import java.util.List;

import com.google.gwt.user.client.rpc.RemoteService;
import com.google.gwt.user.client.rpc.RemoteServiceRelativePath;

@RemoteServiceRelativePath("address")
public interface AddressService extends RemoteService {
  public List<Address> getAddresses();
}

Listing 4 shows the AddressServiceAsync interface:

Listing 4. AddressServiceAsync.java
package com.clarity.client;

import java.util.List;

import com.google.gwt.user.client.rpc.AsyncCallback;

public interface AddressServiceAsync {
  public void getAddresses(AsyncCallback<List<Address>> callback);
}

When I call the getAddresses() method on the client through the asynchronous interface, GWT calls the corresponding method through the remote interface on the server. GWT waits for the method on the server to complete, and then it calls back into the asynchronous implementation's callback, as shown in Listing 2.

Finally, I declare the remote servlet in the Web application's deployment descriptor, shown in Listing 5:

Listing 5. WEB-INF/web.xml
package com.clarity.client;
<?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>
  
  <!-- Servlets -->
  <servlet>
    <servlet-name>address</servlet-name>
    <servlet-class>com.clarity.server.AddressServiceImpl</servlet-class>
  </servlet>

  ...
  
  <servlet-mapping>
    <servlet-name>address</servlet-name>
    <url-pattern>/places/address</url-pattern>
  </servlet-mapping>
  
  ...
</web-app>

Notice that the servlet name — address — matches the value that I specified for the @RemoteServiceRelativePath annotation in Listing 3. That match couples the remote RPC interface defined in Listing 3 with the servlet implemented in Listing 6.

Server-side database code

The AddressServiceAsync and AddressService interfaces reside on the client. On the server, I've implemented some unremarkable code using POJOs and Hibernate to access addresses stored in a database. The POJO is shown in Listing 6:

Listing 6. Address.java
package com.clarity.client;

import java.io.Serializable;

public class Address implements Serializable {
  private static final long serialVersionUID = 1L;
  private Long id;
  private String description, address, city, state, zip;

  public Address() {
    // you must implement a no-arg constructor
  }
  
  public Address(Long id, String address, String city) {
    this.address = address;
    this.city = city;
    this.id = id;
  }

  public String toString() {
    return address + " " + city + ", " + state + zip;
  }
 
  // Setters and getters for String properties are omitted in the interest of brevity 
}

Listing 7 shows the corresponding Hibernate code:

Listing 7. AddressServiceImpl.java
package com.clarity.server;

import java.util.List;

import org.hibernate.HibernateException;
import org.hibernate.Session;

import com.clarity.client.AddressService;
import com.clarity.client.Address;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;

@SuppressWarnings("unchecked")
public class AddressServiceImpl extends RemoteServiceServlet
  implements AddressService {
  private static final long serialVersionUID = 1L;

  public List<Address> getAddresses() {
    List<Address> addresses = null;
    try {
      Session session = HibernateUtil.getSessionFactory()
        .getCurrentSession(); 
      session.beginTransaction();
      addresses = (List<Address>)session.createQuery("from Address Address ")
        .list();
      session.getTransaction().commit();   
    } catch (HibernateException e) {
      e.printStackTrace();
    }
    return addresses;
  }
}

The AddressServiceImpl class, shown in Listing 7, implements the address service's remote interface — AddressService — and extends GWT's RemoteServiceServlet. The getAddresses() method returns all addresses from the database.

The asynchronous nature of RPCs

To populate the address grid initially with the first address from the database, you might think you can do this:

public class Places implements EntryPoint {
final ListBox addresses = new ListBox();
final private AddressGrid addressGrid = 
    new AddressGrid(addresses, addressList);
  ...

  public void onModuleLoad() {
    ...
    getAddresses();

    // this won't work
    addressGrid.setAddress(addressList.get(0));
    ... 
    RootPanel.get().add(hsp);
  }
  ...
}

But that won't work, because the call to getAddresses() is asynchronous. Instead, you must populate the address grid after the RPC returns, like this:

public class Places implements EntryPoint {
  ...
  public void onModuleLoad() {
    ...
    getAddresses();
    ... 
    RootPanel.get().add(hsp);
  }
  public void getAddresses() {
    AddressServiceAsync as = (AddressServiceAsync) GWT
        .create(AddressService.class);

    as.getAddresses(new AsyncCallback<List<Address>>() {
      ...
      public void onSuccess(List<Address> result) {
        ...
        addressGrid.setAddress(addressList.get(0));
      }
    });
  }  
}

Composite widgets

So far, I've created some widgets and populated a list box with addresses from a database. Now I'll add the address grid to the right side of the application's split panel, as shown in Figure 4:

Figure 4. Adding the address grid
Adding the address grid

The address grid, shown on the right in Figure 4, is a composite widget. Listing 8 shows the implementation of the AddressGrid class:

Listing 8. AddressGrid.java
package com.clarity.client;

import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.TextBox;

public class AddressGrid extends Composite {
  private Grid grid = new Grid(6,2);
  private Label streetAddressLabel = new Label("Address");
  private TextBox streetAddressTextBox = new TextBox();
  private Label cityLabel = new Label("City");
  private TextBox cityTextBox = new TextBox();
  private TextBox stateTextBox = new TextBox();
  private Label zipLabel = new Label("Zip");
  private TextBox zipTextBox = new TextBox();
  private Label stateLabel = new Label("State");
  private Button button = new Button();
  private Address address;

  public AddressGrid(final ListBox addresses, String buttonText,
    ClickHandler buttonClickHandler) {
    initWidget(grid);
    button.setText(buttonText);
    
    grid.addStyleName("addressGrid");

    stateTextBox.setVisibleLength(3);
    zipTextBox.setVisibleLength(5);
    cityTextBox.setVisibleLength(15);

    grid.setWidget(0, 0, streetAddressLabel); grid.setWidget(0, 1, streetAddressTextBox);
    grid.setWidget(1, 0, cityLabel);          grid.setWidget(1, 1, cityTextBox);
    grid.setWidget(2, 0, stateLabel);         grid.setWidget(2, 1, stateTextBox);
    grid.setWidget(3, 0, zipLabel);           grid.setWidget(3, 1, zipTextBox);
    grid.setWidget(5, 0, button);

    button.addClickHandler(buttonClickHandler);
  }
  
  void setAddress(Address address) {
    this.address = address;
    streetAddressTextBox.setText(address.getAddress());
    cityTextBox.setText(address.getCity());
    stateTextBox.setText(address.getState());
    zipTextBox.setText(address.getZip());
  }
  
  public Address getAddress() {
    return address;
  }
  public Button getButton() {
    return button;
  }
}

Composite widgets, as their name suggests, are widgets that are composed of other widgets. The address grid is composed of a Grid, which is populated with labels, text boxes, and a button. When you construct an address grid, you supply the text displayed on the button and a click handler for the button.

The address grid is a reusable depiction of an address, to which you can attach some functionality. In this case, that functionality is implemented in the places application with an event handler.


Event handling

The places application has two event handlers: one that populates the address grid when you select an address from the address list box, and another that creates a window when you click the address grid's Show button. Both of those event handlers are shown in Listing 9, which is an updated version of Listing 1:

Listing 9. Places.java, take 3
package com.clarity.client;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ChangeHandler;
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.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.HorizontalSplitPanel;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.RootPanel;

public class Places implements EntryPoint {
  final ListBox addresses = new ListBox();
  final HorizontalSplitPanel hsp = new HorizontalSplitPanel();
  final ArrayList<Address> addressList = new ArrayList<Address>();
  final AddressGrid addressGrid = new AddressGrid("Show", new ShowPlaceHandler());

  public void onModuleLoad() {
    hsp.add(addresses);
    hsp.add(addressGrid);
    hsp.setSplitPosition("175px");
    
    getAddresses();
    
    addresses.addChangeHandler(new ChangeHandler() {
      public void onChange(ChangeEvent e) {
        addressGrid.setAddress(addressList.get(addresses.getSelectedIndex()));
      }
    });

    RootPanel.get().add(hsp);
  }

  public void getAddresses() {
    AddressServiceAsync as = (AddressServiceAsync) GWT
        .create(AddressService.class);

    as.getAddresses(new AsyncCallback<List<Address>>() {
      public void onFailure(Throwable caught) {
        GWT.log("Can't access database", caught);
      }

      public void onSuccess(List<Address> result) {
        Iterator<Address> it = result.iterator();
        
        while (it.hasNext()) {
          Address address = it.next();
          addresses.addItem(address.getAddress());
          addressList.add(address);
        }
        addresses.setVisibleItemCount(result.size());
        addressGrid.setAddress(addressList.get(0));
      }
    });
  }  

  private class ShowPlaceHandler implements ClickHandler {
    private String[] urls;

    public void onClick(ClickEvent event) {
      final WeatherServiceAsync ws = (WeatherServiceAsync) 
        GWT.create(WeatherService.class);
      
      final MapServiceAsync ms = (MapServiceAsync) GWT.create(MapService.class);
      final Address address = addressGrid.getAddress();

      addressGrid.getButton().setEnabled(false);
      
      ms.getMap(address.getAddress(), address.getCity(), address.getState(), 
        new AsyncCallback<String[]>() {
          public void onFailure(Throwable arg0) {
            Window.alert(arg0.getMessage());            
          }
          public void onSuccess(final String[] urls) {
            addresses.setEnabled(true);
            ws.getWeatherForZip(address.getZip(), true, 
              new AsyncCallback<String>() {
              public void onFailure(Throwable arg0) {
                Window.alert(arg0.getMessage());
                done();
              }
            
              public void onSuccess(String weatherHTML) {
                PlaceDialog dialog = new PlaceDialog(addressGrid.getAddress(), 
                    urls, weatherHTML);
                dialog.setPopupPosition(200, 200);
                dialog.show();
                done();
              }            
            });
          }
        });
    }
    private void done() {
      addressGrid.getButton().setEnabled(true);
    }
  }
}

The modified places application in Listing 9 adds a change handler to the addresses list box. That change handler updates the address grid to reflect the address that the user selects from the list box.

The application also instantiates an instance of AddressGrid with text and a click handler for the grid's button. That click handler is implemented in the ShowPlaceHandler class, which nests RPCs to the map and weather Web services. When both RPCs have completed, the event handler creates a dialog containing a map and the weather forecast for the selected address. That dialog is an instance of PlaceDialog, which I will discuss in Part 2.

Notice that Listing 9 uses handlers, not listeners. GWT originally implemented Swing's familiar listener pattern for event handling: You implemented a listener interface, sometimes through an adapter class that implemented empty methods so that you didn't need to implement all of the interface's methods. Starting in GWT 1.6, listeners are replaced with handlers. Handlers are quite similar to listeners, except that handlers define only one method, and handlers are always passed a single event object. Handlers, which listen for only a single type of event, are more fine-grained than listeners. Also, listeners are passed the widget that fired the event, whereas handlers get more information through an event object that contains event-specific information, including the widget that fired the event.

At this point, the places application has a fair amount of functionality. I should have some tests to test that functionality, so next I'll show you how to test your Ajax calls with GWT.


Ajax testing

GWT provides integration with JUnit, which makes it easy to test your application. The easiest way to begin is to run GWT's junitCreator script, which creates skeletal tests for your application. Here's how I run it to create skeletal tests for the Places application:

junitCreator -junit /Developer/Java/Tools/junit-4.5/junit-4.5.jar 
   -module com.clarity.Places 
   -eclipse Places  com.clarity.client.PlacesTest

The junitCreator script creates a test directory and skeletal tests, as shown in Figure 5:

Figure 5. Testing directory structure
Testing directory structure

Notice that I have deliberately created the test class in the same package, although not the same directory, as the implementation of the application's user interface. That makes it easier for the test class to access member variables of the UI classes, while also keeping the test code separate from the UI code. Putting the test class in a different directory, but in the same package as the UI, is a common GWT idiom.

Now that GWT has created a skeletal test class, I'll provide the implementation. I create an instance of the application, programmatically manipulate widgets, and verify the results. Listing 10 shows how to test the places application's ability to fetch addresses from the database:

Listing 10. Testing RPCs
package com.clarity.client;

import com.google.gwt.junit.client.GWTTestCase;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.ListBox;

public class PlacesTest extends GWTTestCase {

  public String getModuleName() {
    return "com.clarity.Places";
  }

  public void testGetAddresses() {
    Places demo = new Places();
    demo.getAddresses();
    final ListBox addresses = demo.addresses;

    new Timer() {
      public void run() {
        assert (addresses.getItemCount() == 6);
        assert (demo.addressList.size() == 6);
        finishTest();
      }
    }.schedule(10000);

    delayTestFinish(20000);
  }
}

Listing 10 implements a common GWT idiom that uses a timer, along with calls to finishTest() and delayFinishTest(), to test asynchronous calls to the server. The testGetAddresses() method:

  1. Creates an instance of the places application.
  2. Calls the application's getAddresses() method.
  3. Asserts that the application has subsequently populated the addresses list box with six addresses.
  4. Asserts that the application has subsequently populated the application's address list with six addresses.

Because the call to getAddresses() is asynchronous, I must wait for it to finish before I can check the number of items in the addresses list box, so I make that check in a timer that runs 10 seconds after the timer is created. That 10 seconds gives the application enough time to complete the RPC. The call to delayTestFinish() tells GWT to wait 20 seconds for me to call finishTest(). If I don't call finishTest() in 20 seconds, the test times out with an error.


Next time...

GWT is best suited for creating desktop-like applications that are replete with amenities such as drag-and-drop, windows and dialogs, and interactive widgets such as viewports. Although it's a simple application, the places application illustrates the potential for building such an application with GWT. So far I've shown you some fundamentals of GWT, including RPCs and database access, implementing composite widgets, event handling, and Ajax testing. In Part 2, you'll learn about some advanced GWT features, including sinking events, implementing GWT modules, and using event previews.


Download

DescriptionNameSize
Source code for the places applicationj-gwtfu-part1-code690KB

Resources

Learn

Get products and technologies

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Java technology on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Java technology, Web development
ArticleID=424696
ArticleTitle=GWT fu, Part 1: Going places with Google Web Toolkit
publish-date=09012009