Saturday, February 13, 2010

The simplest possible GWT application using UiBinder

I've been thinking about building a application to help my daughters practice math. The idea is to generate a simple daily quiz that they take online. The application should send them an SMS or email reminder to take the quiz, and then track their results. Each completed quiz should earn them points (number of correct answered problems) that they can redeem with me (in exchange for something to-be-decided). Each day they fail to get over some threshold or fail to take the quiz at all, they lose some number of number of points.

Google App Engine seems like a good fit for implementing this app, and anyway I've been wanting to try it out and also learn how to use GWT. I just installed the latest Eclipse plugin (App Engine 1.3.1 and GWT 2.0.2).

Where to begin? Whenever I learn a new technology, I find it helpful to start with the simplest possible scenario and then iterate, making sure I understand each additional layer of complexity. A logical starting point was just to build an app that outputs static HTML.

GWT 2.0 introduced a new declarative technique for defining the UI in XML markup rather than code. The UiBinder documentation has examples but doesn't have a complete tutorial that assumes no previous knowledge of GWT. I started with the Mail sample app and pared it down to the simplest possible example.

The first step was to create a new Web Application Project in Eclipse using org.sample.uitest as the default project package. Then I deleted all of the boilerplate code that gets generated for the new project, leaving a src directory with empty client, server and shared packages. I also removed the servlet mapping from war/WEB-INF/web.xml.

The first file that was clearly necessary was the 'module definition' in src/org/sample/uites/Uitest.gwt.xml. The simplest possible module definition inherits the basic toolkit functionality and stylesheet and defines an entry point into the application:

<module rename-to='uitest'>

  <inherits name='com.google.gwt.user.User'/>

  <inherits name='com.google.gwt.user.theme.standard.Standard'/>

  <entry-point class='org.sample.uitest.client.Uitest'/>

</module>

Next I needed to implement the entry point class. I started by copying the entry point from the Mail sample app and paring it down:

package org.sample.uitest.client;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.DockLayoutPanel;
import com.google.gwt.user.client.ui.RootLayoutPanel;

public class Uitest implements EntryPoint {

  interface Binder extends UiBinder { }

  private static final Binder binder = GWT.create(Binder.class);

  public void onModuleLoad() {

    DockLayoutPanel outer = binder.createAndBindUi(this);

    Window.enableScrolling(false);
    Window.setMargin("0px");

    RootLayoutPanel root = RootLayoutPanel.get();
    root.add(outer);
  }
}

The Binder requires a matching layout file named Uitest.ui.xml:

<ui:UiBinder
  xmlns:ui='urn:ui:com.google.gwt.uibinder'
  xmlns:g='urn:import:com.google.gwt.user.client.ui'>

  <g:DockLayoutPanel unit='EM'>

    <g:center>
      <g:HTML>
        <p>UI Test</p>
      </g:HTML>
 
    </g:center>

  </g:DockLayoutPanel>

</ui:UiBinder>

Finally, an HTML page named Uitest.html is required in the war directory to 'host' the application:

<!doctype html>

<html>
  <head>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
    <title>UI Test</title>
    <script type="text/javascript" language='javascript' src='uitest/uitest.nocache.js'></script>
  </head>
  <body>
    <noscript>
      <div style="width: 22em; position: absolute; left: 50%; margin-left: -11em; color: red; background-color:white; border: 1px solid red; padding: 4px; font-family: sans-serif">
        Your web browser must have JavaScript enabled
        in order for this application to display correctly.
      </div>
    </noscript>
  </body>
</html>

At this point my project consisted of five files, including WEB-INF/web.xml. I ran the project in Eclipse and was able to see the message in the browser.

Out of curiosity, I looked at the requests in Firebug when the page loads. The initial response is simply an unmodified version of the 'host' page, which loads uitest.nocache.js to actually render the page. It appears that uitest.nocache.js executes an onload handler that in turn triggers a request for hosted.html?uitest. I didn't try very hard to decipher the obfuscated JavaScript, but it appears that it is writing a SCRIPT tag to load the dynamic content.