This page last changed on Mar 14, 2007 by bowens.

The Google Web Toolkit (GWT)

GWT is a breath of fresh air for javascript developers. In a nutshell, you build the web page in Java and GWT will translate the Java to Javascript. Automatically, you have cross-browser compatability and tons of cool ajax/javascript UI widgets.

Overview of How GWT Works

GWT comes with three tools:

  • A project generator
  • A compiler
  • An all in one shell for development and testing.

The first thing you will do for any project, is use the project generation tool that will create the initial files you will need. It will also create the compiler and shell tool that you will use ectensively.

Directory Structure

I will not describe how to run the project generation tool, but I will show you the output so you get a feel of what you will be working with. When you create your project, called GeoServer for example, you will have the following directory structure:

+ project/
  + bin/
  + lib/
  + src/
    + org/
      + geoserver/
        + client/
            + Geoserver.java
        + public/
            + Geoserver.html
        + server/
        + Geoserver.gwt.xml
  + test/
  + tomcat/
  + www/
  + .classpath
  + .project
  + Geoserver.launch
  + Geoserver-compile.cmd
  + Geoserver-shell.cmd

All of your Java UI files are under src/org/geoserver/client, with the entry point class Geoserver.java. All of your GWT specific java files live here and they are all translated into Javascript.
The main HTML file, Geoserver.html, is located under src/org/geoserver/public. This is where all of the images and other HTML files will live.
Finally, there is the Geoserver-compile.cmd and Geoserver-shell.cmd*. The compiler will compile all of your Java files into Javascript for deployment in a production environment. The shell script will start up an instance of Tomcat, and run your application inside. You will use this most for your development because you can debug in it.

Finally, there is a src/org/geoserver/server directory, this you will have to create yourself. In here is where you put all of your servlets, which I will talk about later.

Code/Compile/Test workflow

When you make a change to your Java files, or even HTML files, all you have to do is start up the shell script and refresh the built in browser. So, if you make a little one-line change and you already have the shell started up, you just have to hit refresh and the change will take effect. This make development very quick and easy to test.

Widgets

GWT comes with a bunch of widgets that you can use, widgets such as:

  • panels
  • buttons
  • text fields
  • plain HTML
  • lists
  • menus
  • For the whole list, look here.

In your Java file, you can create these, configure them, and set the CSS style they are to use.
Here is an example use of a widget:

Widgets to HTML Integration

Creating widgets in the Java file is one thing, but how do we position them in our main HTML page? It is very easy. In GWT, once you have created your widget, you can give it an ID and inform the main Root Panel (your HTML page) of that widget. Then in the HTML page, you just create an element, either a table cell or a div, and give it an ID that matches the one of your widget.
In this code example we define the widget first in GWT and give it an ID of "myWidget":

// the HTML widget is just plain HTML, it does nothing special
HTML mine = new HTML("This contains some <b>plain</b> html. You can put whatever <blink>tags</blink> you want.");
RootPanel.get("myWidget").add(mine);

Then in our HTML file we give a <div> the ID that matches our widget's ID ("myWidget"):

<body>
    <div id="myWidget"></div>
</body>

Simple Example

For a simple example we will just create a small Java file that has some widgets (button and some HTML).

First the Java file:

package org.geoserver.client;

public class Geoserver implements EntryPoint {
    public void onModuleLoad() {
        HTML myHtml = new HTML("This contains some <b>plain</b> html. You can put whatever <blink>tags</blink> you want.");

        // Make a new button that does something when you click it.
        Button b = new Button("Hit me!", new ClickListener() {
          public void onClick(Widget sender) {
            Window.alert("You hit the button.");
          }
        });

        // Add it to the root panel.
        RootPanel.get("myButton").add(b);

        RootPanel.get("myHtmlWidget").add(myHtml);
    }
}

In the HTML file we put:

<html>
<head>
<title>My Page</title>
<meta name='gwt:module' content='org.geoserver.Geoserver'>
</head>
<body>
    <div id="myHtmlWidget"></div>
    <br>
    <!-- my button is here -->
    <div id="myButton"></div>
</body>

This will create a simple HTML page with our text string and a button.

Javascript and GWT Communication

There might be times when you want to have GWT access Javascript functions or variables, or have Javascript in your HTML file call GWT. It is possible to do this with native methods in your GWT file.
There are two directions of communication and each is written in a native JSNI format. First is the GWT to Javascript call:

    public native void callInit()/*-{
        $wnd.init();
    }-*/;

This is in your GWT Java file and will call the external init() function in your HTML file. It is possible to pass variables too:

    public native void callInit(int i)/*-{
        $wnd.init(i);
    }-*/;

Then in javascript we just need:

function init(i) {
    alert("You entered "+i);
}


The next direction of communication is from Javascript to GWT Java. Once again, we have to create a native method:

    public native void createExternalCalls(Geoserver x)/*-{
        $wnd.setId = function (id) {
            x.@org.geoserver.client.Geoserver::setMyId(I)(id);
        };
    }-*/;

    // This is called from the native method
    public void setMyId(int id) {
        // do something with the ID
    }

You only need to call the method once, usually in the onModuleLoad() method.
The native call is in JSNI, and it takes a bit of practice to get used to.
What it is doing is it takes in the Geoserver object which is the entry point class we are working with. We then reference a method in that class called setMyId(int id).
The format of the parameter in the native method is the type (I) integer, and then the actual variable (id). This will call our real method setMyId(int id).

RPC calls with AJAX and Servlets

Having a UI is great, but you will want to save the information to the server. This is done with AJAX calls to a servlet that will persist the changes.
GWT makes talking with servlets fairly easy.
There are two Interfaces and one Class that you have to create:

  • MyService (interface)
  • MyServiceImpl (implementation class of MyService)
  • MyServiceAsync (interface)

Here is the plumbing diagram from the GWT site that describes it quite well: http://code.google.com/webtoolkit/documentation/com.google.gwt.doc.DeveloperGuide.RemoteProcedureCalls.PlumbingDiagram.html

As an example, lets create a service called SampleService:

public interface SampleService extends RemoteService {
    public void setId(int id);
    public int getId();
}

We create an Aysynchronous interface that is almost exactly the same:

public interface SampleServiceAsync {
    public void setId(int id);
    public void getId();
}

Then we create the servlet itself:

public class SampleServiceServlet extends RemoteServiceServlet implements SampleService {
    
    private int myId;

    public void setId(int id) {
        myId = id;
    }

    public int getId() {
        return myId;
    }
}

The RemoteServiceServlet class handles all the serialization for you and will forward the requests to the appropriate methods of yours.

Next we want to be able to make a call to the servlet. To do this we need to instantiate our Service class, give it a callback object, and then make the actual call:

SampleServiceAsync service = (SampleServiceAsync) GWT.create(SampleService.class);
ServiceDefTarget endpoint = (ServiceDefTarget) service;
String moduleRelativeURL = GWT.getModuleBaseURL() + "/sample";
endpoint.setServiceEntryPoint(moduleRelativeURL);

final AsyncCallback getIdCallback = new AsyncCallback() {
  public void onSuccess(Object result) {
    // do something with the result: add it to a widget
  }

  public void onFailure(Throwable caught) {
    // do some UI stuff to show failure
  }
};

service.getId( 1234 , getIdCallback);

Once the service is set up, you just need to create a callback object that will handle the result when it arrives back to the client. This is the aychronous part of the code. And as you can see, calling the service is fairly clear and easy to understand (once you have created the service itself).




Applying GWT to GeoServer

Lets take a look at how we could implement GeoServer's UI in GWT.

It is good to take note first that GWT can not be split up to be completely modular, at least not easily. But it could be worth considering having the UI un-modular since user interfaces should be separate from the model of your program anyways, and often the UI will interact with all parts anyways. So having it completely pluggable and modularized could lead to a very confusing user interface. An example of cross cross-module dependency for the UI is the "Map Preview" page, especially if it is improved to have direct links to the FeatureTypes and CoverageTypes, to allow for a neat and simple single interface to all your data.

anyways...

GeoServer needs the UI so the user can configure the settings, add datasets, and preview their data.
Several main modules of the UI are:

  • Server config
  • WMS config
  • WFS config
  • WCS config
  • Data config
    • DataStores
    • CoverageStores
    • FeatureTypes
    • CoverageTypes
    • Styles
  • Examples and demos

There are a couple ways we can go about the UI. The first would be to have one entry class and have an entire, real, AJAX/javascript UI, similar to a web-based deskop app such as gmail.com. However, having played with many widgets at once and separate modules, this can get quite cumbersome.
The second way would be to have several pages, each with moderate amounts of AJAX/javascript functionality, but all still mostly separate from each other except for navigation links.

Lets assume we have a separate page for each module. In that page we will link to other pages, allow the editing of information through user-friendly javascript widgets, and send/retrieve information through AJAX to our servlet. Lets look at the layout using the WFS module as our example:

+ project/
  + src/org/geoserver/
        + client/
            + services/
                + WfsService.java
                + WfsServiceAsync.java
            + Geoserver.java
            + WFS.java
        + public/
            + Geoserver.html
            + wfs.html
        + server/
            + WfsServiceServlet.java
        + Geoserver.gwt.xml

The files to note are:

  • WFS.java (Our GWT class)
  • wfs.html (HTML page where the information will go)
  • WfsService.java (interface)
  • WfsServiceAsync.java (interface)
  • WfsServiceServlet.java (class implements WfsService.java)
  • Geoserver.gwt.xml (where we register our servlet)

The Servlet

The servlet will act as a bean, with some get and set methods so we can transfer information to and from the server. The information would be the specific configuration options for the WFS module such as the Description and Contents (enabled/disabled, srsName as XML, strict CITE test conformance, service level, online resource).
The interface for such a servlet service could look like this:

public interface WfsService extends RemoteService {

    public boolean isEnabled();
    public void setEnabled(boolean enabled);

    public boolean isSrsNameAsXML();
    public void setSrsNameAsXML(boolean asXml);

    public boolean isStrictCiteConformance();
    public void setStrictCiteConformance(boolean conformant);

    public String getServiceLevel();
    public void setServiceLevel(String level);

    public String getOnlineResource();
    public void setOnlineResource(String url);

}

Then, an implementation of the service would look like this (I'm just going to show four methods):

public class WfsServiceServlet extends RemoteServiceServlet implements WfsService {
    public String getServiceLevel() {
        ServletContext context = this.getServletContext();
        WFSConfig config = (WFSConfig) context.getAttribute(WFSConfig.CONFIG_KEY);

        int level = config.getServiceLevel(); // get the value
        return new String(level);
    }

    public void setServiceLevel(String level) {
        ServletContext context = this.getServletContext();
        WFSConfig config = (WFSConfig) context.getAttribute(WFSConfig.CONFIG_KEY);

        config.setServiceLevel( (new Integer(level)).intValue() ); // save the value
    }

    public boolean isEnabled() {
        ServletContext context = this.getServletContext();
        WFSConfig config = (WFSConfig) context.getAttribute(WFSConfig.CONFIG_KEY);

        boolean enabled = config.isEnabled(); // get the value
        return enabled;
    }

    public void setEnabled(boolean enabled) {
        ServletContext context = this.getServletContext();
        WFSConfig config = (WFSConfig) context.getAttribute(WFSConfig.CONFIG_KEY);

        config.setEnabled( enabled ); // save the value
    }
}

This servlet then just has to mirror the functionality of the existing Struts Action classes that we have already. So porting over to GWT will be very easy in this aspect.

Register the Servlet

We need to tell GWT about the servlet. This is done in the Geoserver.gwt.xml file. In it, it will have the following lines:

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

	<!-- Specify the app entry point class. -->
	<entry-point class='org.geoserver.client.Geoserver'/>
  	<servlet path="/config_wfs" class="org.geoserver.server.WfsServiceServlet"/>
</module>

Set Up the Main GWT Class

In our WFS.java GWT class is where we integrate everything and design the UI.

The class will have an onModuleLoad() method where we will declare most of our widgets. For WFS configuration, we will need some check boxes, text fields, and a drop down list:

public class Geoserver implements EntryPoint {

    // widgets
    HTML header;
    CheckBox enabledBox;
    Button saveButton

    // internal info
    boolean enabled;
    String serviceLevel;

    public void onModuleLoad() {

        WfsServiceAsync service = createWfsService();

        // get the 'isEnabled' value so we can set it in the UI
        service.isEnabled( getEnabledCallback() );

        // create a generic header for the page
        header = new HTML( GeoServerUiUtils.getGenericHeader() );

        enabledBox = new CheckBox("Enabled");
        enabledBox.setChecked(enabled);

        // Hook up a listener to find out when it's clicked.
        enabledBox.addClickListener(new ClickListener() {
            public void onClick(Widget sender) {
                enabled = ((CheckBox) sender).isChecked();
            }
        });

        // Make a new button that saves the data when you click it.
        saveButton = new Button("Save", new ClickListener() {
            public void onClick(Widget sender) {
                service.setEnabled(enabled, getSaveCallback());
            }
        });



        // Add the widgets to the root panel.
        RootPanel.get("gwt_header").add(header);
        RootPanel.get("gwt_serviceEnabled").add(enabledBox);
        RootPanel.get("gwt_saveButton").add(saveButton);
    }

    private WfsServiceAsync createWfsService() {
        WfsServiceAsync service = (SampleServiceAsync) GWT.create(WfsService.class);
        ServiceDefTarget endpoint = (ServiceDefTarget) service;
        String moduleRelativeURL = GWT.getModuleBaseURL() + "/config_wfs";
        endpoint.setServiceEntryPoint(moduleRelativeURL);

        return service;
    }


    private AsyncCallback getSaveCallback() {
        final AsyncCallback callback = new AsyncCallback() {
	    public void onSuccess(Object result) {
		 // do nothing; the data has been saved
	    }

            public void onFailure(Throwable caught) {
                // do some UI stuff to show failure
                // redirect to the error page
	    }
	};
        return callback;
    }


    private AsyncCallback getEnabledCallback() {
        final AsyncCallback callback = new AsyncCallback() {
	    public void onSuccess(Object result) {
		 // check the result to see if 
		 Boolean b = new Boolean(result);
                 this.enabled = b.booleanValue();
	    }

            public void onFailure(Throwable caught) {
                // do some UI stuff to show failure
	    }
	};
        return callback;
    }
}

The HTML Page

The last thing we have to set up is the main HTML page that will have the embedded GWT widgets.
It can be simple or complex, but the important thing is to include elements with matching ID's to the GWT widgets:

<html>
<head>
<title>WFS Configuration</title>
<meta name='gwt:module' content='org.geoserver.WFS'>
</head>
<body>
Welcome to <a href="http://geoserver.org">GeoServer</a>.
<br>
    <div id="gwt_header"></div>
    <br>
    WFS Configuration:
    <hr>
    Enabled <div id="serviceEnabled"></div><br>
    
    <!-- Save button is here -->
    <div id="saveButton"></div>
</body>

How This Will Look For All of GeoServer

Lets take a quick look at what the file structure will look like if we were to implement the new UI in GWT. For each module there will be:

  • Module_gwt.java
  • Module.html
  • ModuleService.java
  • ModuleServiceAsync.java
  • ModuleServiceServlet.java

There will be two interfaces, one servlet, one GWT class, and one HTML file.

HTML  <-------->  GWT  <-------->  Servlet

As an example, lets look at a few modules: WFS, WMS, WCS, Data/DataStores

  HTML    <-------->    GWT     <-------->  Servlet  <-------->  GeoServer
__________________________________________________________________________________________
WFS.html  <-------->  WFS.java  <-------->  WfsConfigServlet  <-------->  GeoServer
WMS.html  <-------->  WMS.java  <-------->  WmsConfigServlet  <-------->  GeoServer
WCS.html  <-------->  WCS.java  <-------->  WcsConfigServlet  <-------->  GeoServer
DataStore.html  <-------->  DataStore.java  <-------->  DataStoreConfigServlet  <-------->  GeoServer

The datastore module can and should be linked in with the FeatureType module for cases when you add a shapefile. Same goes for CoverageStores.

Conclusion

GWT has a little bit of a learning curve, but once you get it, it is very easy to use (it's a little easier than Wicket and miles easier than Struts/JSP). There is a little documentation out there, but not much (yet), and a book is on its way.
GWT is by far easier than JSPs, and there is no struts madness. However you have to implment the "Controller" part of the UI in the GWT files, which are also the View. But for an application such as the configuration interface for GeoServer, not much of a controller is needed: simple success and failure messages when saving configurations, and links to other configuration pages.
With GWT you also get snazzy javascript interfaces that can make the experience much easier for the user and can allow for better workflow. Plus you get to code in Java and HTML, no learning JSPs. It has come up several times where we have wanted to implement something simple with straight HTML, but it has been a pain to hook it in with Struts, this will make it a lot easier. GWT also supports browser history as well so the user can still use the back button. Not to mention you automatically get cross browser functionality, so you don't have to learn the quirks of each browser (and believe me, there are a lot of quirks!).
You can also develop and debug your GWT code in Eclipse, a definite plus!
GWT isn't modular because you have to list your servlets in a single file so GWT can talk to them. But if it is decided that the UI really does have to be linked together in a nice and understandable way (non-modularized), then GWT is the way to go.

Pitfalls

Watch out for overly large GWT files. Divide up the widgets into Composite widgets.
Your GWT files are your "Controller" (in the Model View Controller design) and can get complicated if you make them do too much. But it shouldn't be too complicated with GeoServer.
Errors in GWT are sometimes meaningless or hard to understand and leave you scratching your head. Make small changes and test frequently!

Document generated by Confluence on May 14, 2014 23:00