Skip to main content

Kickstarting Google Web Toolkit on the Client Side

June 27, 2006

{cs.r.title}






At the 2006 JavaOne event, a certain company by the name of Google (they're very big in the search market, apparently) caused quite a buzz with the shock announcement of a new open source API for developing Ajax-heavy web applications. GWT--Google Web Toolkit--is Google's attempt to tame Ajax, making it a viable option for small-team and/or cost-conscious web applications development. And with a name like Google behind it, perhaps it's no wonder that so many are casting a inquisitive eye over this strange new technology.

But GWT is so new, the ink has hardly had time to dry on its Apache license, and there's a dearth of introductory material to consult (Robert Cooper's "Working with the Google Web Toolkit" is a start).

This quick-start tutorial aims to translate some of the knowledge gained from my monkeying about with GWT into a useful text which will get other developers up and running quickly. To keep things nice and simple, we'll focus on only client-side matters. But fear not--you'll be surprised by what GWT can do without continual feeding via server round-trips.

What Is Google Web Toolkit?

Let us begin, naturally enough, at the beginning.

Whether you're a web developer or not, Ajax is a term you cannot fail to have heard over the last 12 to 18 months. Loosely put, the term describes a collaboration of technologies in which client-side DOMs are married to JavaScript, backed by on-demand web requests for XML data. Hailed by some as heralding a new age of feature-rich web applications, damned by others as a tangled mess with more implementation quirks than one can shake a stick at--what few deny is that professional-quality Ajax coding is hard and painful.

Now Google, the company largely responsible for popularizing Ajax, hope to soothe some of these woes by creating a simple and consistent, but powerful, level playing field. That level playing field is GWT.

So what's on offer here?

  • From a GUI point of view, GWT does for Ajax what AWT (Abstract Window Toolkit) did for the desktop: it creates a set of abstract, browser-neutral components (or "widgets," in GWT parlance) ready made for rich client-side interfaces.
  • GWT also attempts to tame traditional web application headaches, such as browser page history issues.
  • GWT provides support for JSON and an RPC API, designed to take much of the sting out of communicating and exchanging sophisticated data models between client and server.
  • Perhaps most tantalizing of all, GWT promises to bring the power of Java testing, profiling, and debugging into developing Ajax applications, thanks to a schizophrenic approach that uses both Java- and JavaScript-centric environments.

This last point is particularly novel. In GWT, one creates an application in 100 percent Java against a set of APIs that mimic the end-user runtime environment. One then runs the application--still as bytecode--in a test environment. Conventional Java tools may be used to debug and assess the performance of the classes. Finally, when it's time to deploy your creation, the Java code is re-compiled into JavaScript suitable for a variety of popular web browsers.

Sound too good to be true? Well perhaps it is. We'll see.

By the way, if you want to follow the article against the full source code, you should download it before we begin.

Getting Started: Project One

Getting started with GWT really is as simple as downloading and unpacking the development kit from Google's site, and reading their short but sweet introduction. The kit is available for Linux and Windows at the time of writing, although a Mac version will no doubt appear soon. The code in this tutorial was tested using GWT for both Windows and Linux, version 1.0.20.

The development kit includes APIs, source code, examples, and some brief documentation. Having unpacked its contents, you may want to add the resulting top-level directory to your shell path, as it contains some useful shell scripts/DOS batch files.

Note: Google has provided its API documentation in a proprietary format, which isn't really as useful as the full-blown Javadocs. Extracting the source from the .jar (it's embedded with the class files) and running Javadoc produces warnings and a clutter of extra implementation packages/classes. Here's hoping someone will prune down the output.

Right, so first off, we need to create a new GWT application, and the aptly named applicationCreator script will set up everything we need for working from the command line. If you're using Eclipse, you'll need to follow the instructions for setting up an application integrated into that particular IDE. At the time of writing, no other IDE is supported, although this will surely change.

Create a directory for our demo applications, cd into it, and then create the structure for our first demo using the following shell command:

applicationCreator -out . demo1.client.HelloWorld

The above will set up a new project in the current directory (the -out switch) called HelloWorld, where HelloWorld is the name of the project's main class, and demo1.client is the package in which it lives. Note: the *Creator tools insist that the package name end with .client. If you try to create a project inside a package that doesn't use a .client suffix, the scripts will politely ask you to add it.

And now the code. I've provided the source code if you want it, although I recommend you try to build this first example from scratch yourself. If you look in the new src/ directory, you'll see a directory structure all set up and ready to take your source files. You'll also see Google's own default source file in place of the class you specified, which you might want to wipe before editing afresh.

To try Project One online, click here.

package demo1.client;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.RootPanel;

public class HelloWorld implements EntryPoint
{   public void onModuleLoad()
    {   RootPanel root = RootPanel.get();
        root.add(new Label("Borag thung, Earthlet!"));
    }
}

This listing shows a basic GWT application. All we do is fetch the root widget of the UI, and add a label widget to it--very familiar to anyone who has worked with AWT or Swing. By and large, interfaces are built up by adding components (widgets) to containers (panel widgets), and the top-level widgets are hooked into a root panel. The interface EntryPoint can be thought of as a more type-safe alternative to public static void main(String[]), or the init() method found in applets. Its onModuleLoad() method will be called when the application starts.

onModuleLoad() appears to be the only lifecycle method. Unlike with applets, where some or all initialization can be deferred until start(), GWT gives you only this one-time chance to kick start your application.

By the way, if you know what "Borag thung, Earthlet!" means, then you obviously had the same misspent childhood as I did.

To try the code, we run the HelloWorld-shell script that was generated for us by applicationCreator. This script, named in honor of the class name used when creating the application, compiles and runs the application for us, launching the emulation application in the process. Two windows will open: one is a log, where issues such as compilation errors will appear; the other is a pseudo-browser that emulates the Ajax environment. Actually, it appears to use the new Java Desktop Integration Components to link your system's native browser component--IE or Mozilla Gecko--into the application. So you are indeed seeing a real browser render your pages.

One of the little things that caught me out at first was the Refresh button on the browser. When I got compilation errors, I was shutting the toolkit "shell" down and re-running it to build my modified code--a slow and awkward process. After doing this a few times, it dawned on me that clicking the innocuous Refresh button on the browser window forced the application to be speedily recompiled and reloaded.

When you are happy with your application, you may choose to compile it into JavaScript. The HelloWorld-compile script will do this for you from the command line, or you can click the button provided on the emulator toolbar.

Before we move on, take a look at the directory structure inside src, where you'll find a public directory parallel to client. Inside public you'll discover HelloWorld.html. This is our application's HTML file--by modifying this, we can change the page content around our application, and style the components inside of it. But more on that later. You'll note the HTML also has various slots, which are place markers allowing us to interleave GWT content with HTML markup. The RootPanel.get(String) method, a sibling of the empty parameter version we used above, returns a named slot embedded inside of a page.

The Adventure Begins: Project Two

Like what you see so far? Fancy a bit more of a challenge? How about a trivial adventure game, of the Colossal Cave variety?

It's a simple enough job; unlikely to really tax GWT, but ideal to demonstrate how to build a GUI against Java code with a some meat on it. The game itself is trivial, and the mechanics are hidden behind a class called GameEngine. The purpose of the project isn't to learn how to write an adventure game, but rather to focus on binding the engine into a client-side user interface. As such, we'll be treating the engine class very much as a "black box"--a class we instantiate, and then make method calls on without worrying about how it works. Be warned: GameEngine is more of a comical homage to adventure games past than a serious game. When you play it, don't expect too much!

Although we won't cover the mechanics of GameEngine in this article, it's worth noting its internals will still be eventually compiled to JavaScript by GWT along with the rest of our code, ready for deployment--a testament to the power of the toolkit.

If you are following along at home, you'll need to create a second application, as per the command line below. You can use the same base directory as Project One, if you wish. Then unpack the source code and move it into the created directories.

applicationCreator -out . demo2.client.Adventure

Note: If you check the scripts generated by applicationCreator, you'll see the path to your GWT installation hardcoded into them. Even the Ant build files created by projectCreator have a hardcoded path, making it hard to share GWT projects with fellow developers. Of course, it would be possible to hand-hack each of these generated files to use environment variables instead, but it's a shame a standardized solution isn't already provided.

To try Project Two online, click here.

package demo2.client;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.user.client.ui.*;

public class Adventure implements EntryPoint
{   private HTML transcriptH;
    private TextBox inputTB;
    private ScrollPanel transcriptPan;
    private Label whatNowL;

    private GameEngine engine;
    private StringBuffer outputBuffer;

    public void onModuleLoad()
    {   transcriptH = new HTML();
        transcriptH.setStyleName("transcript");
        inputTB = new TextBox();
        inputTB.setSize("100%",null);
        inputTB.setStyleName("inputTB");
        whatNowL = new Label("> What now?");
        whatNowL.setStyleName("whatNowL");

        inputTB.addKeyboardListener(new KeyboardListenerAdapter()
        {   public void onKeyDown(Widget sender,
                        char keyCode, int modifiers)
            {   if(keyCode==KeyboardListener.KEY_ENTER)
                {   String line = inputTB.getText();
                    inputTB.setText("");
                    outputBuffer.append("<b>&gt; ")
                        .append(line)
                        .append("</b><br/>\n");
                    engine.execute(line);
                    updateTranscript();
                }
            }
        });

        VerticalPanel vPan = new VerticalPanel();
        vPan.setSize("100%",null);
        vPan.add(transcriptH);
        vPan.add(whatNowL);
        vPan.add(inputTB);

        transcriptPan = new ScrollPanel(vPan);
        transcriptPan.setAlwaysShowScrollBars(true);
        transcriptPan.setSize("100%","25em");
        transcriptPan.setStyleName("transcript");

        RootPanel.get().add(transcriptPan);

        outputBuffer = new StringBuffer();
        engine = new GameEngine(outputBuffer);
        updateTranscript();
    }

    private void updateTranscript()
    {   transcriptH.setHTML(outputBuffer.toString());
        transcriptPan.ensureVisible(inputTB);
    }
}

This example starts with four widgets: first, an HTML widget for holding pure markup content; next, a text field; after that, a scroll pane for creating a scrollable area around content; and finally, a "What next?" prompt label. The opening paragraph of code in the onModuleLoad() method sets up most of these components--two methods are frequently invoked:

  • setSize() unsurprisingly sets the desired size for the widget, although you might be intrigued to see strings being passed instead of integers. This is because this method takes a CSS size rather than the pixel sizes you might be used to with AWT and Swing.
  • setStyleName() also relates to CSS--you'll recall the HTML file mentioned previously, which lived in the public directory? Well, the setStyleName() method is a way of binding the widgets created in the Java code with stylesheet rules associated with that HTML file.

Below the widget initialization we define an anonymous inner class for keyboard listening. Again, this simple code should look familiar to anyone who has worked with AWT, Swing, or any other event-heavy Java API. We register a listener with the widget we wish to receive events from--in this case, keyboard events from the input text field. The listener waits for an Enter key, and then fetches the contents of the text field and feeds it into the engine. The engine updates the output StringBuffer. (You can't see that, but trust me, it does!) Next, the listener updates the transcript widget and scrolls it down so that the text field, which should always be the bottom component, is showing.

Finally, in onModuleLoad(), we glue all the widgets together, first by stacking the transcript, prompt, and text field vertically atop each other inside of a panel; then by including that panel inside a scrollable pane; and finally, attaching the scrollable pane into the HTML document at its root. The result is shown in Figure 1.

Miniscule Cave game layout in GWT
Figure 1. Miniscule Cave game layout in GWT

As you can see, passing null to either or both parameters for setSize is valid. It's not documented, but the effect seems to be to ignore that dimension, allowing the default or inherited size to prevail.

Note: The setSize method takes some explaining. At first, I assumed container panels used it as a guide for sizing their children. Thus, if I had three widgets side by side in a HorizontalPanel, sized 100%, 100%, and 50%, the widgets would be sized in 2:2:1 proportions. This isn't necessarily the case--even if the widgets start out that size, they can quickly change once their contents are updated. It seems the real use of setSize is to inform the widget how to make use of whatever space is allocated to it by its parent container. To reliably control the parent container when sizing its children, the setCellWidth and setCellHeight methods found in some panel classes should be used. So, to produce the aforementioned 2:2:1 proportions, one would use setCellWidth with 40%, 40%, and 20%, and then setSize("100%",null) on each child widget to ensure they fill out their allotted space.

The following listing presents said HTML document from public/. You'll recognize how the CSS styles, highlighted in bold, marry with the setStyleName() calls in the Java above. This enables us to style our GWT components in the HTML.

<html>
    <head>
        <title>Wrapper HTML for Adventure</title>
        <style>
            body,td,a,div,.p{font-family:arial,sans-serif}
            div,td{color:#000000}
            a:link,.w,.w a:link{color:#0000cc}
            a:visited{color:#551a8b}
            a:active{color:#ff0000}
            .transcript{ font-family:monospace;
                background-color:#ffffcc }
            .inputTB{ font-family:monospace }
            .whatNowL{ font-family:monospace; color:Blue }

        </style>
        <meta name='gwt:module'
          content='net.sf.smmj.game1.Adventure'>
    </head>
    <body>
        <script language="javascript"
            src="gwt.js"></script>
    </body>
</html>

GWT in Space: Project Three

Let us end our whistle-stop tour of GWT's user interface capabilities with something a bit different and fun. The following example will introduce absolute positioning, mouse events, and using utility classes from Google's slimmed-down version of the java.util package.

As before, run applicationCreator as shown below, then move the source code into place from the archive provided.

applicationCreator -out . demo3.client.MoonWalk

To try Project Three online, click here for a maximum compatibility version. Alternatively, users of Mozilla, Firefox, Opera, or IE7+ might like to try the slightly enhanced version here.

package demo3.client;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.*;

public class MoonWalk implements EntryPoint
{   private AbsolutePanel absPan;
    private FocusPanel focPan;
    private Widget backW,mountW,midW;
    private int scrollPos=0;
    private Image dukeImg,shadowImg;

    public void onModuleLoad()
    {   absPan = new AbsolutePanel();
        absPan.setSize("320px","200px");

        focPan = new FocusPanel();
        focPan.add(absPan);
        focPan.addMouseListener(new DukeHandler());

        // FIXME: Error checking on image loading!
        backW=repeatWidth("./bg.png");
        _add(backW,0,0);
        mountW=repeatWidth("./mount.png");
        _add(mountW,0,100);
        midW=repeatWidth("./mid.png");
        _add(midW,0,150);
        dukeImg = new Image("./duke.png");
        _add(dukeImg,130,10);
        shadowImg = new Image("./shadow.png");
        _add(shadowImg,130,170);
        updateDuke(10);

        _add(new Image("./blank.png"),320,0);

        RootPanel.get().add(focPan);
        new AnimationTimer().scheduleRepeating(50);
    }

    private void _add(Widget w,int x,int y)
    {   absPan.add(w);  absPan.setWidgetPosition(w,x,y);
    }

    private Widget repeatWidth(String loc)
    {   HorizontalPanel hp = new HorizontalPanel();
        hp.add(new Image(loc));
        hp.add(new Image(loc));
        return hp;
    }

    private void updateDuke(int y)
    {   if(y>10 && y<80)
        {   absPan.setWidgetPosition(dukeImg,130,y);
            y=(y-10)/14;  shadowImg.setStyleName("duke"+y);
        }
    }

    private class AnimationTimer extends Timer
    {   public void run()
        {   scrollPos=(++scrollPos)%320;
            absPan.setWidgetPosition(
                backW, 0-scrollPos, 0 );
            absPan.setWidgetPosition(
                mountW, 0-((scrollPos*2)%320), 100 );
            absPan.setWidgetPosition(
                midW, 0-((scrollPos*3)%320), 150 );
        }
    }

    private class DukeHandler extends MouseListenerAdapter
    {   public void onMouseMove(Widget w,int x,int y)
        {   updateDuke(y);
        }
    }
}

The above code, when compiled, has Duke floating majestically over a parallax scrolling landscape, complete with fading shadow where available. (Don't laugh--it only took me half an hour with GIMP to create the graphics!)

Right at the start of the onModuleLoad() we create two panels: an AbsolutePanel for positioning our widgets using pixel co-ordinates, and a FocusPanel for receiving mouse events. The first is added into the second (although I assume it would work the other way round, too). The next paragraph loads and adds a host of images to our AbsolutePanel instance: the starscape background, the far-off crystal mountains, the rough terrain middle ground, then, in front of them all, Duke and his shadow. Next, a blank image is added to mask the stuff scrolling on the right-hand side (try the example with this line commented out to see what I mean). And finally, everything is hooked into the web page and an animation timer is started. The AnimationTimer.run() method is called periodically, and updates the scroll position before moving the three parts of our parallax scenery accordingly.

Note: AbsolutePanel proudly claims that its children can overlap. However, it isn't entirely clear how one is supposed to set the Z order position--the order of display, from front to back. A brief experiment revealed calling the setWidgetPosition(Widget,int,int) method appears to move the widget to the front-most "layer." Experimentation also revealed that the add(Widget,int,int) method resulted in the widget not being displayed, hence the use of add and setWidgetPosition as two separate steps in the example.

Incidentally, you may be wondering about the use of a blank image. This is because GWT doesn't current support viewports. A viewport is a window showing only a region of its contents, while allowing the contents to be scrolled. The closest thing GWT has at the time of writing to a viewport is the ScrollPanel class, which automatically displays scroll bars once the contents become larger than the view, and does not support horizontal scrolling. The blank image appears over all others, positioned at the right-hand edge of the scene it constrains the animation to a sharp rectangle by masking the other images as they scroll beneath. A viewport would have clipped the images to prevent them from bleeding out of the scene rectangle as they moved, removing any need for a masking image.

The cumulative result of all this layout is shown in Figure 2.

Moon Walk demo in GWT
Figure 2. Moon Walk demo in GWT

When the mouse moves over the application (specifically, the focPan widget), DukeHandler.onMouseMove() is called, which defers to the updateDuke() method. Duke is moved vertically within a strict limit of 10-80 pixels, and the position within this range is translated into one of five possible transparency levels for Duke's shadow, as represented by the .duke0-4 styles in the HTML document. Regrettably, the CSS opacity attribute isn't supported by all browsers, so some may see a fixed shadow no matter what.

Be warned; getAbsoluteTop() and getAbsoluteLeft() return coordinates based upon the browser document window, not the page. So clicking in the extreme top-left corner will return (0,0), no matter how you scroll the page. com.google.gwt.user.client.Window has methods for reading the width and height of the browser document window, but not its scroll offset, so there's currently no way to translate a window coordinate into a page coordinate. Also, we aware that mouse events deliver their X and Y coordinates as relative to the widget generating the event (the widget the event listener is registered with) so these may need to be translated into absolute window coordinates by adding the absolute position of the source widget to both X and Y.

You'll notice that I've provided two versions of the demo. This serves to demonstrate a point--namely that it is all too easy to be lulled into a false sense of security when using GWT. The original version of Moon Walk was written on Linux using a Mozilla/Gecko engine, where alpha transparency in both image data and CSS is supported. PNG and alpha support under other browsers is weaker, notably Internet Explorer, which (excluding bleeding edge versions) doesn't support the PNG format natively. The lesson is: GWT tries its best, but some things it just can't fix.

Conclusion

Hopefully the above has whetted your appetite for GWT. The library is far from perfect, as demonstrated by its "beta" tag at the time of writing. But with the support of Google and the open source community, it might quickly grow into a viable platform for developing complex Ajax applications with rich UI requirements. If nothing else, GWT's unusual Java/JavaScript solution has stimulated some debate in fields that have been all too stagnant of late. Hopefully, this will flow over into fresh thinking and innovation in other toolkits.

Fingers crossed.

Resources

width="1" height="1" border="0" alt=" " />
Simon Morris started coding professionally back when 1 MB of RAM was considered decadent.
Related Topics >> Programming   |