Dienstag, 2. November 2010

Creating Re-usable Composites Using GWT

Currently I stumbled across the task of creating re-usable composites using the Google Web Toolkit (GWT).

During the development I experienced the lack of a good/helpful tutorial on the internet - especially when it comes to "advanced" topics like I18N, CSS, ...
Therefore I want to share my experiences with you hoping it helps someone out there! As always comments, feedback is very much appreciated. ;)

The idea behind this tutorial is to create a simple GWT composite including its server logic within one project. Package it as a JAR-file and re-use it within another GWT project.

In detail this requires the following steps:
  • Creation of a simple GWT composite including a simple service on the server side
  • Inclusion of the I18N (internationalization) mechanism to this composite
  • Inclusion of some basic styling using CSS-Resources
  • Packing the composite into a sinlge JAR-file
  • Re-Use of the composite within a second GWT project
  • Ways to extend/alter the behavior of the composite within the second project
  • Download of the sample projects ;)
Tools used:
  • Eclipse Galileo
  • GWT 2.0.3
  • Java 1.6

First of all let's create a GWT project called GWT-base. This project will contain a simple HelloComposite. The HelloComposite will contain two buttons, one for saying Hello (a clientside method running within javacript) and one button saying Good Bye that relies on a callback to the server. A very simple setting but it has everything to explain the techniques behind.

The Base-project containing the Hello composite will look like:


Here is a screen of the basic project structure of the base project.


So lets start and explain a few things. The center piece of this base project is the class CompositeDispatcher. This class can be seen as a dispatcher that returns instances to service implementations on the server and to the current internationalization message file.

package com.base.client;

import com.google.gwt.core.client.GWT;

public class CompositeDispatcher {

  private static BaseMessages I18NMessages;
  
  private static GreetingServiceAsync greetingService;
  
  public static GreetingServiceAsync getGreetingService() {
    
    if (greetingService == null) {
      greetingService = GWT.create(GreetingService.class); 
    }
    
    return greetingService; 
    
  }
  
  public static BaseMessages getI18NMessages() {
  
    if (I18NMessages == null) {
      I18NMessages = GWT.create(BaseMessages.class);
    }
    
    return I18NMessages;
    
  }
  
}

For creating the composite there exist two abstract classes - AbstractAsyncCallback and AbstractComposite.
The idea behind AbstractAsyncCallback is to provide a default behavior for all AsyncCallback for the onFailure(...) method.
The idea behind the AbstractComposite is to provide functionality all further composites have to implement. First of all every composite has to implement the abstract init(..) method that is automatically called by the constructor and which is responsible for creating the composite. Secondly the constructor makes the following call - BaseResources.INSTANCE.css().ensureInjected() - which is responsible for ensuring that the CSS-file/CSS-Reource has been injected into the project.

package com.base.client;

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

public abstract class AbstractAsyncCallback implements AsyncCallback {

  @Override
  public void onFailure(Throwable caught) {
    //Default Behavior
    Window.alert("ERROR: " +caught.getMessage());
  }

  @Override
  public abstract void onSuccess(T result);

}

package com.base.client;

import com.google.gwt.user.client.ui.Composite;

public abstract class AbstractComposite extends Composite {

  public AbstractComposite() {
   BaseResources.INSTANCE.css().ensureInjected();
   init();   
  }
  
  protected abstract void init();
  
}

The Server side implementation GreetingServiceImpl is very simple. It just contains one method returning a plain old string.

package com.base.server;

import com.base.client.GreetingService;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;

/**
 * The server side implementation of the RPC service.
 */
@SuppressWarnings("serial")
public class GreetingServiceImpl extends RemoteServiceServlet implements
    GreetingService {

  public String sayGoodBye() {
    return "Server says GoodBye";
  }
  
}

So lets take a look at the implementation of HelloComposite.

package com.base.client;

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.Button;
import com.google.gwt.user.client.ui.VerticalPanel;

public class HelloComposite extends AbstractComposite {

  protected VerticalPanel pnlLayout;
  
  protected Button btnGreet;
  
  protected Button btnGoodBye;
  
  protected ClickHandler greetHandler;
  
  protected ClickHandler goodByeHandler;
  
  protected AsyncCallback goodByeCallback;
  
  public HelloComposite() {
   
  }
  
  @Override
  protected void init() {
     
    pnlLayout = new VerticalPanel();
    pnlLayout.addStyleName("gwtBase-helloComposite");
  
    btnGreet = new Button();
    btnGreet.setText(CompositeDispatcher.getI18NMessages().btnSayHello());
    btnGreet.addClickHandler(getGreetHandler());
    btnGreet.addStyleName("gwtBase-btn-Height");
  
    btnGoodBye = new Button();
    btnGoodBye.setText("Say Goodbye");
    btnGoodBye.addClickHandler(getGoodByeHandler());
    
    pnlLayout.add(btnGreet);
    pnlLayout.add(btnGoodBye);
    
    initWidget(pnlLayout);
    
  }

  protected ClickHandler getGreetHandler() {
    
    if (greetHandler == null) {
      greetHandler = new ClickHandler() {
        
        @Override
        public void onClick(ClickEvent event) {
          Window.alert("Hello");          
        }
        
      };
    }
    
    return greetHandler;
  }

  protected ClickHandler getGoodByeHandler() {
    
    if (goodByeHandler == null) {
      goodByeHandler = new ClickHandler() {
        
        @Override
        public void onClick(ClickEvent event) {
        
          CompositeDispatcher.getGreetingService().sayGoodBye(getGoodByeCallback());
          
        }
        
      };
    }
    
    return goodByeHandler;
  }
  
  protected AsyncCallback getGoodByeCallback() {
    
    if (goodByeCallback == null) {
      
      goodByeCallback = new AbstractAsyncCallback() {

        @Override
        public void onSuccess(String result) {
          Window.alert(result);
        }
        
      };
      
    }
    return goodByeCallback;
    
  }
    
}

As you can see, all widgets (VerticalPanel and Buttons) have been declared as protected members and the class inherits from AbstractComposite. The two clickHandlers and the AsyncCallback are class members as well. There are getters for all members which follow the design pattern of lazy initialization. This is needed because of one fact. The init(..) method which is called from the base class - AbstractComposite - gets called before the members would be initialized by the constructor. The init(..) method is responsible for building and styling the composite and makes a call initWidget(..) at the end to initialize the widget.
The idea behing the lazy initialization getters is, that their behaviour can easily be exchanged/overwritten in child classes. For example if we want to define another clickHandler(..) in an inherited project this can easily be performed by overwritting the getClickHandler(...) method. More on this later... ;)

Last but not least we have the classes needed for accessing the CSS-file(s). BaseCss and BaseResources. Not very thrilling but listed for completeness!

package com.base.client;

import com.google.gwt.resources.client.CssResource;

public interface BaseCss extends CssResource {
    
}

package com.base.client;

import com.google.gwt.core.client.GWT;
import com.google.gwt.resources.client.ClientBundle;
import com.google.gwt.resources.client.CssResource.NotStrict;

public interface BaseResources extends ClientBundle {

  BaseResources INSTANCE = GWT.create(BaseResources.class);
  
  @NotStrict
  @Source("GWT_Base.css")
  BaseCss css();
  
}

At this point the project GWT-base is runnable on its own and with small additions to the HTML file and the GWT Entry Point it can be started as a GWT Web Application.

We do not want to do this right now, but we want to export the created composite to a JAR-file and use it within a new GWT project.
Using Eclipse Export Command we created a JAR-File out of the whole project. It is very important to package the sources as well as the generated class files. A common mistake is the absence of the source files. This will result in an error because the GWT compiler of the project using this composite will not be able to generate JavaScript code!



So now we have a JAR-file containing our Hello Composite, its CSS Resource and the I18N message files.

Let's create a second GWT project called GWT-Extended. It uses the gwtBase.jar file as a resource.
After some alterations to the web.xml and the GWT_Extended.gwt.xml we can reuse the server logic of the GWT base project as well as the HelloComposite.


  
  
  
  
  
  
  
  
  
  




  
 
  
    greetServlet
    com.base.server.GreetingServiceImpl
  
  
  
    greetServlet
    /gwt_extended/greet
  
  
  
  
    GWT_Extended.html
  



package com.extended.client;

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

/**
 * Entry point classes define onModuleLoad().
 */
public class GWT_Extended implements EntryPoint {
  
  /**
   * This is the entry point method.
   */
  public void onModuleLoad() {
   
    HelloComposite helloComposite = new HelloComposite();
    RootPanel.get("compositeContainer").add(helloComposite);
        
  }
}

As a last step I want demonstrate how to extend the HelloComposite in this inherited project.
Therefore we want to create a GreetingComposite that looks just like the HelloComposite except that it has another text on the greet button and that its clickHandler(..) behaves differently.

package com.extended.client;

import com.base.client.HelloComposite;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.Window;

public class GreetingComposite extends HelloComposite {

  @Override
  protected void init() {
   super.init();
   btnGreet.setText("Greet Me");
  }
  
  @Override
  protected ClickHandler getGreetHandler() {
    if (greetHandler == null) {
      greetHandler = new ClickHandler() {
        
        @Override
        public void onClick(ClickEvent event) {
          Window.alert("Good Day Sir!");
        }
        
      };
    }
    return greetHandler;
  }  
  
}

All other feature CSS and I18N have been inherited from the parent HelloComposite.
Even the init(..) method uses the base functionality and just overwrites the text for the greet button afterwards. By overwritting the getGreetHandler(...) method we provide a different functionality for the GreetingComposite without the need for any further changes. In the same manner we could overwrite the AsyncCallback, add other widgets or style existing widgets differently.

And so at the end we have an extended project that uses the original HelloComposite as well as an extended GreetingComposite all together!



As promised here are the links to the sample projects: GWT-Base.zip and GWT-Extended.zip

That's it for now. Looking forward to comments, additions, discussion or whatever! ;)

End of Version 1 (02.11.2010)