Ease AJAX development with the Google Web Toolkit

Use the GWT to create a simple AJAX application

Google Web Toolkit (GWT) is a Java development framework that seeks to ease AJAX (Asynchronous JavaScript and XML) application development. GWT allows you to develop and debug AJAX-based applications in Java using common Java development tools and deploy the same applications as client-side HTML and JavaScript, and server-side Java. GWT also simplifies typical AJAX-style RPC (remote procedure call) conversations between client-side JavaScript and server-side Java services.

In this article, I discuss the basics of GWT and show how Java developers can use the GWT to create a simple AJAX application to retrieve search results from a remote API to display in a browser.

Introducing the Google Web Toolkit (GWT)

The Google Web Toolkit is an application development platform composed of a Java class library, AJAX-style UI components called widgets, an RPC-based request/response communication framework, and an integrated debugging environment. GWT provides a subset of the java.lang and java.util packages, along with a Java API that facilitates component-based GUI development, which can be compiled to HTML and JavaScript for deployment to a browser.

GWT applications can be executed in two modes:

  1. Hosted mode: This mode executes a GWT application as a regular Java application, allowing standard Java debugging. To support hosted mode, GWT provides a proprietary Web browser that can interact with a JVM.
  2. Web mode: This mode allows a GWT application to be deployed and executed as native JavaScript and HTML, generated from Java source code by the GWT Java-to-JavaScript compiler.

GWT has four major components/libraries, as follows:

  • GWT Java-to-JavaScript compiler: This component translates Java source code to native JavaScript and HTML
  • GWT hosted Web browser: This component allows GWT applications to be executed as Java code within a JVM-aware Web browser
  • JRE emulation library: This library provides subsets of the java.lang and java.util packages
  • GWT Web UI class library: This library is a set of proprietary interfaces and classes, called widgets, that can be used to create browser-based GUI components

GWT applications are required to provide an "entry-point" class and discreet units of configuration data, bundled together, to form an application "module." Each module consists of a configuration file, named according to the pattern module-name.gwt.xml, and a class that implements the com.google.gwt.core.client.EntryPoint interface, which acts as the application's main entry point. GWT's runtime JavaScript library relies on this module-based structure being in place. A typical module configuration file is illustrated in the following snippet:

 <module>

   <!-- Inherit the core Web Toolkit stuff. -->
   <inherits name='com.google.gwt.user.User'/>

   <!-- Specify the app entry point class. -->
   <entry-point class='com.example.client.MyApp'/>
  
</module>

A module's entry-point class must implement the com.google.gwt.core.client.EntryPoint interface and provide a no-arg constructor. When a module is loaded, its entry-point class is instantiated and its onModuleLoad() method is invoked by the GWT framework.

Getting started with GWT

To get started using GWT, first download and extract the GWT SDK for your specific operating system from Google.

GWT's library of classes and interfaces, or widgets, can be used to construct UI components for an AJAX application. The layout of widget components is managed by container widgets, known as panels, that can be nested inside other panel components.

The following illustrates how a button widget can be instantiated and embedded within a container panel called MyContanerPanel:

 final com.google.gwt.user.client.ui.Button button =
    new com.google.gwt.user.client.ui.Button("Click me");

  button.addClickListener(new com.google.gwt.user.client.ui.ClickListener()
  {
    public void onClick(com.google.gwt.user.client.ui.Widget sender)

    {
      System.out.println("The 'Click me' button was clicked");
    }
  });

  com.google.gwt.user.client.ui.RootPanel.get("MyContainerPanel").add(button);

The GUI for a GWT application is composed of Java code similar to the preceding example that can be debugged in hosted mode using standard Java debugging tools. Applications executed in hosted mode run within a proprietary shell embodied by the com.google.gwt.dev.GWTShell class. This class can be executed standalone or within an IDE. When running in hosted mode, GWT executes Java bytecodes within a proprietary browser window, as shown below:

Figure 1. Click on thumbnail to view full-sized image.

Once an application is ready for deployment, the GWT compiler can be used to convert the Java source code into JavaScript, thereby transforming the Java application into an analogous JavaScript application. The com.google.gwt.dev.GWTCompiler class is used to compile a GWT application to JavaScript from the command line.

In respect to GWT, all activity that would typically occur within a Web browser is referred to as client-side processing. When you write client-side code 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 by the GWT compiler described above.

Likewise, all activity that would typically occur on a server host is referred to as server-side processing. When an application interacts with a server, it makes browser (client-side) requests to server-side code using GWT's remote procedure call (RPC) framework.

Creating a GWT application

GWT ships with a command line utility called applicationCreator that automatically generates all the files needed to run a minimal GWT application. These files embody a project outline that can be used as a starting point for an application.

This article demonstrates an application that can search for books from a remote site and display the results in an AJAX-based GUI in a Web browser. To create a skeleton for the application called BookSearch, instigate the applicationCreator utility with the command:

 <GWT_HOME_DIR>applicationCreator -out ./BookSearch com.example.client.BookSearch

Be sure to replace <GWT_HOME_DIR> with the directory name of your GWT installation. The applicationCreator utility generates numerous files in the <GWT_HOME_DIR>BookSearch directory, including some basic client-side functionality in the class com/example/client/BookSearch.java. The utility also generates a hosted mode launch script called BookSearch-shell and a compilation script called BookSearch-compile.

The directory structure for the newly-created BookSearch application is illustrated in the following table:

PackagePurpose
com/example/The project root package contains module XML files. In this case, BookSearch.gwt.xml, which inherits the com.google.gwt.user.User module.
com/example/client/Client-side Java source files and subpackages. In this case, BookSearch.java.
com/example/public/Static resources that can be served publicly. In this case, BookSearch.html, which loads the BookSearch application.

To run the BookSearch skeleton application in hosted mode, execute the BookSearch-shell script. You should see something resembling the following image:

Figure 2. Click on thumbnail to view full-sized image.

Creating the BookSearch application's homepage

The final BookSearch application uses one table with two td elements; one contains a widget for processing search terms and the other, a widget for displaying a list of book-related data. This table is added to BookSearch.html in the com/example/public directory as follows:

 <table width="100%" border="0" summary="Book Search">
  <tr valign="top">
    <td id="searchterm" align="center" width="100%">
    </td>
  </tr>

  <tr valign="top">
    <td id="booklist" align="center" width="100%">
    </td>
  </tr>
</table>

Now, the UI layout can be initialized so that UI widgets can be added.

The following snippet shows the code needed to initialize the UI layout for the BookSearch application:

 public void onModuleLoad()
{
  private static final int VISIBLE_ROWS = 5;

  public void onModuleLoad()
  {
    // Retrieve the panel for the booklist widget
    //
    RootPanel booklistPanel = RootPanel.get("booklist");
    if (booklistPanel != null)
    {
      BookListWidget booklistWidget = new BookListWidget(VISIBLE_ROWS);
      booklistPanel.add(booklistWidget);

      // Retrieve the panel for the searchterm widget
      //
      RootPanel searchtermPanel = RootPanel.get("searchterm");
      if (searchtermPanel != null)
      {
        SearchTermWidget searchTermWidget =
           new SearchTermWidget(booklistWidget);
        searchtermPanel.add(searchTermWidget);
      }
    }
  }
}

All initialization code goes inside the BookSearch class's onModuleLoad() method. The onModuleLoad() method is the only method defined by the com.google.gwt.core.client.EntryPoint interface. This method is invoked when the module is loaded. Notice how the com.google.gwt.user.client.ui.RootPanel class is used to retrieve references to the BookSearch.html elements by their IDs. GWT relies on naming conventions to locate widget classes that map to HTML element IDs. For example, the "booklist" HTML ID is used to locate a widget named "BookListWidget."

Creating the application's client-side behavior

Now that the application's form and structure is established, client-side behavior for the application can be built. Our application's client-side behavior is encapsulated within three main UI widget instances:

  1. A widget instance for handling search-term processing
  2. A container widget instance that will hold a search-service provider and a paginated list
  3. A paginated-list widget instance that will incorporate a set of widget instances to form a navigation bar and a paginated list of books

The following code illustrates the widget that handles the retrieval of a booklist and contains the paginated list:

 public class BookListWidget extends com.google.gwt.user.client.ui.Composite
{
  private final PageableListWidget pageableListWidget;
  private String searchTerm = "";

  /**
   *
   * @param visibleRows
   */
  public BookListWidget(int visibleRows)
  {
    String[] columns = new String[]{"Title",
                                    "ISBN",
                                    "Edition",
                                    "MSRP"};
    String[] styles = new String[]{"title",
                                   "isbn",
                                   "edition",
                                   "msrp"};
    pageableListWidget = new PageableListWidget(bookSearchProvider,
                                                columns,
                                                styles,
                                                visibleRows);
    initWidget(pageableListWidget);
  }

  protected void onLoad()
  {
    pageableListWidget.refresh();
  }

  /**
   *
   * @param searchTerm
   */
  protected void setSearchTerm(String searchTerm)
  {
    if (this.searchTerm.equals(searchTerm))
    {
      // No change
      //
      return;
    }

    this.searchTerm = searchTerm;

    pageableListWidget.refresh();
  }
}

The BookListWidget class extends the com.google.gwt.user.client.ui.Composite class to facilitate an aggregate UI component incorporating one or more associated widgets. In this case, only one nested composite-based widget, PageableListWidget, is used.

The PageableListWidget class also extends the com.google.gwt.user.client.ui.Composite class and contains multiple child widgets, including a custom navigation-bar widget and a com.google.gwt.user.client.ui.Grid widget that handles a list of book-related data. The navigation-bar widget incorporates an instance of the com.google.gwt.user.client.ui.DockPanel widget and several instances of the com.google.gwt.user.client.ui.Button class.

The following illustrates the PageableListWidget class:

 public class PageableListWidget extends com.google.gwt.user.client.ui.Composite
{
  private final RowDataAcceptor acceptor = new RowDataAcceptorImpl();
  private final NavBar navbar = new NavBar();
  private final DockPanel outer = new DockPanel();
  private final SearchProvider provider;
  private int startRow = 0;
  private final Grid grid = new Grid();

  /**
   * Navigation Bar widget
   */
  private class NavBar extends Composite
    implements ClickListener
  {
    public final DockPanel bar = new DockPanel();
    public final Button gotoFirst = new Button("First", this);
    public final Button gotoNext = new Button("Next", this);
    public final Button gotoPrev = new Button("Prev", this);
    public final HTML status = new HTML();

    public NavBar()
    {
      initWidget(bar);
      bar.setStyleName("navbar");
      status.setStyleName("status");

      HorizontalPanel buttons = new HorizontalPanel();
      buttons.add(gotoFirst);
      buttons.add(gotoPrev);
      buttons.add(gotoNext);
      bar.add(buttons, DockPanel.EAST);
      bar.setCellHorizontalAlignment(buttons,
                                     DockPanel.ALIGN_RIGHT);
      bar.add(status, DockPanel.CENTER);
      bar.setVerticalAlignment(DockPanel.ALIGN_MIDDLE);
      bar.setCellHorizontalAlignment(status,
                                     HasAlignment.ALIGN_RIGHT);
      bar.setCellVerticalAlignment(status,
                                   HasAlignment.ALIGN_MIDDLE);
      bar.setCellWidth(status, "100%");

      // Initially disable prev & first buttons
      //
      gotoPrev.setEnabled(false);
      gotoFirst.setEnabled(false);
    }

    public void onClick(Widget sender)
    {

      // handle nav-bar button clicks
    }
  }

  public PageableListWidget(SearchProvider provider,

                            String[] columns,
                            String[] columnStyles,
                            int rowCount)
  {

    this.provider = provider;
    initWidget(outer);
    grid.setStyleName("table");
    outer.add(navbar, DockPanel.NORTH);
    outer.add(grid, DockPanel.CENTER);
    initTable(columns, columnStyles, rowCount);
    setStyleName("BookSearch-PageableListWidget");
  }

  /**
   *
   * @param columns
   * @param columnStyles
   * @param rowCount
   */
  private void initTable(String[] columns,
                         String[] columnStyles,
                         int rowCount)
  {
    // Set up the header row to one greater than the number of visible rows
    //
    grid.resize(rowCount + 1, columns.length);
    for (int i = 0, n = columns.length; i < n; i++)
    {
      grid.setText(0, i, columns[i]);
      if (columnStyles != null)
      {
        grid.getCellFormatter().setStyleName(0, i, columnStyles[i] + " header");
      }
    }

  }

  public void refresh()
  {
    // Disable buttons temporarily to stop the user from overrunning the table
    //
    navbar.gotoFirst.setEnabled(false);
    navbar.gotoPrev.setEnabled(false);
    navbar.gotoNext.setEnabled(false);

    setStatusText("Please wait...");

    // update table
    updateTable(startRow, getDataFromService());
  }

  private void updateTable(int startRow, String[][] data)
  {
    int destRowCount = getDataRowCount();
    int destColCount = grid.getCellCount(0);
    assert (data.length <= destRowCount) : "Too many rows";

    int srcRowIndex = 0;
    int srcRowCount = data.length;
    int destRowIndex = 1; // skip navbar row
    for (; srcRowIndex < srcRowCount; ++srcRowIndex, ++destRowIndex)
    {
      String[] srcRowData = data[srcRowIndex];
      assert (srcRowData.length == destColCount) : " Column count mismatch";
      for (int srcColIndex = 0; srcColIndex < destColCount; ++srcColIndex)
      {
        String cellHTML = srcRowData[srcColIndex];
        grid.setText(destRowIndex, srcColIndex, cellHTML);
      }
    }

    // Clear any remaining table rows
    //
    boolean isLastPage = false;
    for (; destRowIndex < destRowCount + 1; ++destRowIndex)
    {
      isLastPage = true;
      for (int destColIndex = 0; destColIndex < destColCount; ++destColIndex)
      {
        grid.clearCell(destRowIndex, destColIndex);
      }
    }

    // Synchronize the nav buttons
    //
    navbar.gotoNext.setEnabled(!isLastPage);
    navbar.gotoFirst.setEnabled(startRow > 0);
    navbar.gotoPrev.setEnabled(startRow > 0);
  }

  public void setRowCount(int rows)
  {
    grid.resizeRows(rows);
  }

  private int getDataRowCount()
  {
    return grid.getRowCount() - 1;
  } 
}

The SearchTermWidget extends the com.google.gwt.user.client.ui.Composite class to contain an aggregate of a label, a text box, and a button. The text box contains search terms, and the button initiates each search.

The following illustrates the SearchTermWidget class:

 public class SearchTermWidget extends com.google.gwt.user.client.ui.Composite
{
  private final HorizontalPanel outer = new HorizontalPanel();
  private BookListWidget booklistWidget = null;
  private TextBox searchTermTxtBox = null;

  public SearchTermWidget(final BookListWidget booklist)
  {
    initWidget(outer);
    setStyleName("BookSearch-SearchTermWidget");


    this.booklistWidget = booklist;

    Label lbl = new Label("Search term: ");
    lbl.setHeight("1.5em");

    searchTermTxtBox = new TextBox();
    searchTermTxtBox.setHeight("1em");
    searchTermTxtBox.setText("");

    Button searchBtn = new Button("Search", new ClickListener()
    {
      public void onClick(Widget sender)
      {
        booklistWidget.setSearchTerm(searchTermTxtBox.getText());

      }
    });
    searchBtn.setHeight("1.5em");

    HorizontalPanel hp = new HorizontalPanel();
    hp.setHorizontalAlignment(HasAlignment.ALIGN_CENTER);

    hp.add(lbl);
    hp.add(searchTermTxtBox);
    hp.add(searchBtn);

    outer.add(hp);
    outer.setCellVerticalAlignment(hp, HasAlignment.ALIGN_MIDDLE);
    outer.setCellHorizontalAlignment(hp, HasAlignment.ALIGN_CENTER);
  }
}

Now the application is prepared to call the server-side service.

1 2 Page
Join the discussion
Be the first to comment on this article. Our Commenting Policies
See more