Serendipity - working with gwt-platform and smartGWT

In the previous post, we created a GWT starter project and refactored it in order to take advantage of the best practices supported by gwt-platform. Now, we're ready to move from a starter project to a sample application (called Serendipity) so that we can learn more about how to work with gwt-platform and smartGWT.

In this post, we'll:

  1. Create a GWTP starter project
  2. Update the starter project so that it uses smartGWT
  3. Create a Sign In page that uses GWT widgets
  4. Create an Error dialog box that uses GWT widgets
  5. Create a Main page using smartGWT widgets
  6. Create an Activities page (nested Presenter and View) that uses smartGWT widgets
  7. Test the application in development mode

Note: You can download the sample application from the crmdipity (Se-r-en-dipity) project's download page.

1. Create a GWTP starter project

Follow the steps in this post to create a GWTP starter project.

 

2. Update the starter project so that it uses smartGWT

Add the following libraries to the project:

  • smartgwt.jar
  • smartgwt-skins.jar

Add the libraries to your build path by right-clicking on each of the jar files and selecting "Build Path -> Add to Build Path".

 

We also need to update the project's module definition file:

<?xml version="1.0" encoding="UTF-8"?>
<module rename-to='serendipity'>

  <!-- Inherit the core Web Toolkit stuff.                        -->
  <inherits name='com.google.gwt.user.User'/>
  <inherits name='com.google.gwt.inject.Inject'/>
  <inherits name="com.google.gwt.uibinder.UiBinder" />
  
  <!-- Inherit the default GWT style sheet.                       -->
  <inherits name='com.google.gwt.user.theme.standard.Standard'/>

  <!-- Other module inherits                                      -->
  <inherits name='com.gwtplatform.mvp.Mvp' />
  <inherits name='com.gwtplatform.dispatch.Dispatch' />
  
  <inherits name="com.smartgwt.SmartGwtNoTheme"/>  	
  <inherits name="com.smartclient.theme.enterpriseblue.EnterpriseBlue"/>
  <inherits name="com.smartclient.theme.enterpriseblue.EnterpriseBlueResources"/>  
  
  <!-- Specify the app entry point class.                         -->
  <entry-point class='au.com.uptick.serendipity.client.Serendipity'/>

  <!-- Specify the paths for translatable code                    -->
  <source path='client'/>
  <source path='shared'/>

  <!-- Set user agents											  -->
  <set-property name="user.agent" value="gecko1_8"/> 	
  
  <!-- In any real-world application, you will define at least    -->
  <!-- one locale in addition to the default locale. 			  -->
  <extend-property name="locale" values="en"/>     
  
  <!-- Add German language support								  -->
  <!-- <extend-property name="locale" values="de"/>   	          -->
  
  <define-configuration-property name="gin.ginjector" 
      is-multi-valued="false" />
  <set-configuration-property name="gin.ginjector" 
      value="au.com.uptick.serendipity.client.gin.SerendipityGinjector" />  
  
</module>

And, the application's host file:

<!doctype html>
<html>
  <head>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
    
	<meta name="gwt:property" content="locale=en">

    <link type="text/css" rel="stylesheet" href="Serendipity.css">

    <title>Serendipity</title>
    
    <!-- You must set the variable isomorphicDir to [MODULE_NAME]/sc/ --> 
	<!-- so that the SmartGWT resources are correctly resolved        -->	
	<script> var isomorphicDir = "serendipity/sc/"; </script> 	
    
    <script type="text/javascript" language="javascript" 
	        src="serendipity/serendipity.nocache.js"></script>
  </head>
  <body>
    <!-- OPTIONAL: include this if you want history support -->
    <iframe src="javascript:''" id="__gwt_historyFrame" tabIndex='-1' 
	        style="position:absolute;width:0;height:0;border:0"></iframe>
    
    <!-- RECOMMENDED if your web app will not function without JavaScript enabled -->
    <noscript>
      <div>
        Your web browser must have JavaScript enabled
        in order for this application to display correctly.
      </div>
    </noscript>
  </body>

</html>

3. Create a Sign In page that uses GWT widgets

The first page we want to create is the Sign In page, so we need to create a new View (ViewWithUIHandlers) class, SignInPageView:

...

public class SignInPageView extends ViewWithUiHandlers<SignInPageUiHandlers> implements 
    SignInPagePresenter.MyView {

  private static String html = "<div>\n"
    + "<table align=\"center\">\n"
    + "  <tr>\n" + "<td> </td>\n" + "<td> </td>\n" + "</tr>\n"
    + "  <tr>\n" + "<td> </td>\n" + "<td> </td>\n" + "</tr>\n"
    + "  <tr>\n" + "<td> </td>\n" + "<td> </td>\n" + "</tr>\n"
    + "  <tr>\n"    
    + "    <td colspan=\"2\" style=\"font-weight:bold;\">Sign In</td>\n"
    + "  </tr>\n"
    + "  <tr>\n"
    + "    <td>User name</td>\n"
    + "    <td id=\"userNameFieldContainer\"></td>\n"    
    + "  </tr>\n" 
    + "  <tr>\n"
    + "    <td>Password</td>\n"
    + "    <td id=\"passwordFieldContainer\"></td>\n"    
    + "  </tr>\n" 
    + "  <tr>\n"
    + "    <td></td>\n"
    + "    <td id=\"signInButtonContainer\"></td>\n"  
    + "  </tr>\n"     
    + "  <tr>\n" + "<td> </td>\n" + "<td> </td>\n" + "</tr>\n"
    + "  <tr>\n"
    + "    <td colspan=\"2\">Forget your password?</td>\n"
    + "  </tr>\n"
    + "  <tr>\n"
    + "    <td colspan=\"2\">Contact your Serendipity administrator.</td>\n"
    + "  </tr>\n"    
    + "</table>\n"
    + "</div>\n";

  HTMLPanel panel = new HTMLPanel(html);

  private final TextBox userNameField;
  private final PasswordTextBox passwordField;
  private final Button signInButton;

  @Inject
  public SignInPageView() {
    userNameField = new TextBox();
    passwordField = new PasswordTextBox();
    signInButton = new Button("Sign in");

    userNameField.setText("administrator");

    panel.add(userNameField, "userNameFieldContainer");
    panel.add(passwordField, "passwordFieldContainer");
    panel.add(signInButton, "signInButtonContainer");
  }

  @Override
  public Widget asWidget() {
    return panel;
  }

  @Override
  public String getUserName() {
    return userNameField.getText();
  }
  
  @Override
  public String getPassword() {
    return passwordField.getText();
  }  

  @Override
  public Button getSignInButton() {
    return signInButton;
  }

  @Override
  public void resetAndFocus() {
    userNameField.setFocus(true);
    userNameField.selectAll();
  }
}

The corresponding Presenter class, SignInPagePresenter:

...

public class SignInPagePresenter extends  
    Presenter<SignInPagePresenter.MyView, SignInPagePresenter.MyProxy> implements 
    SignInPageUiHandlers {
  
  private final PlaceManager placeManager;
  private final ErrorDialogPresenterWidget errorDialog;
  
  @ProxyStandard
  @NameToken(NameTokens.signInPage)
  public interface MyProxy extends Proxy<SignInPagePresenter>, Place {
  }
  
  public interface MyView extends View, HasUiHandlers<SignInPageUiHandlers> {
    String getUserName();
    String getPassword();
    Button getSignInButton();
    void resetAndFocus();
  }
  
  @Inject
  public SignInPagePresenter(EventBus eventBus, MyView view, MyProxy proxy, 
      PlaceManager placeManager, ErrorDialogPresenterWidget errorDialog) {
    super(eventBus, view, proxy);
    
    this.placeManager = placeManager;
    this.errorDialog = errorDialog;
    
    getView().setUiHandlers(this);
  }

  @Override
  protected void onBind() {
    super.onBind();
    registerHandler(getView().getSignInButton().addClickHandler(
        new ClickHandler() {
          @Override
          public void onClick(ClickEvent event) {
            sendCredentialsToServer();
          }
        }));
  }

  @Override
  protected void onReset() {
    super.onReset();
    getView().resetAndFocus();
  }

  @Override
  protected void revealInParent() {
    RevealRootLayoutContentEvent.fire(this, this);
 }

  private void sendCredentialsToServer() {
    String userName = getView().getUserName();
    String password = getView().getPassword();
    
    if (FieldVerifier.isValidUserName(userName) && (FieldVerifier.isValidPassword(password))) {
      PlaceRequest myRequest = new PlaceRequest(NameTokens.mainSmartGwtPage);
      
      // If needed, add URL parameters in this way:
      // myRequest = myRequest.with( "key1", "param1" ).with( "key2", "param2" );
      placeManager.revealPlace(myRequest); 
    }
    else {
      showErrorDialog();
    }
  }

  @Override
  public void showErrorDialog() {
    addToPopupSlot(errorDialog);
  }
}

And the UiHandlers class, SignInPageUiHandlers:

...

package au.com.uptick.serendipity.client.view;

import com.gwtplatform.mvp.client.UiHandlers;

public interface SignInPageUiHandlers extends UiHandlers {
  void showErrorDialog();
}

The Sign In page:

 

4. Create an Error dialog box that uses GWT widgets

We also need to create an Error dialog box, so we need to create a new View (PopupViewImpl) class, ErrorDialogPopupView:

...

public class ErrorDialogPopupView extends PopupViewImpl implements MyView {

  interface ErrorDialogPopupViewUiBinder extends
      UiBinder<PopupPanel, ErrorDialogPopupView> {
  }

  private static ErrorDialogPopupViewUiBinder uiBinder = GWT.create(ErrorDialogPopupViewUiBinder.class);

  @UiField
  Button okButton;

  private final PopupPanel widget;

  @Inject
  public ErrorDialogPopupView(EventBus eventBus) {
    super(eventBus);
    widget = uiBinder.createAndBindUi(this);
  }

  @Override
  public Widget asWidget() {
    return widget;
  }
  
  @UiHandler("okButton")
  void okButtonClicked(ClickEvent event) {
    widget.hide();
  }
}

The corresponding Presenter (PresenterWidget) class, ErrorDialogPresenterWidget:

...

public class ErrorDialogPresenterWidget extends
    PresenterWidget<ErrorDialogPresenterWidget.MyView> {

  public interface MyView extends PopupView {
  }

  @Inject
  public ErrorDialogPresenterWidget(final EventBus eventBus, final MyView view) {
    super(eventBus, view);
  }
}

And the UiBinder template, ErrorDialogPopupView.ui.xml:

<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
<ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder"
	xmlns:g="urn:import:com.google.gwt.user.client.ui">

	<g:DialogBox modal="false" >
		<g:caption>
			<b>Error</b>
		</g:caption>
		<g:HTMLPanel>
		  <p>Invalid User name or Password.<br /></p>
			<g:Button ui:field="okButton">Ok</g:Button>
		</g:HTMLPanel>
	</g:DialogBox>
</ui:UiBinder> 

The Error dialog box:

 

5. Create a Main page using smartGWT widgets

The second page we want to create is the Main page, so we need to create a new View class, MainPageSmartGwtView:

...

public class MainPageSmartGwtView extends ViewImpl implements MainPageSmartGwtPresenter.MyView {
  
  // NORTH_HEIGHT = MASTHEAD_HEIGHT + APPLICATION_MENU_HEIGHT + NAVIGATION_PANE_HEADER_HEIGHT
  private static final int NORTH_HEIGHT = 85; 
  private static final int DEFAULT_MENU_WIDTH = 70;
  private static final String DEFAULT_MARGIN = "0px";    
  
  private final Masthead masthead;
  private final ApplicationMenu applicationMenu;
  private final NavigationPaneHeader navigationPaneHeader;
  private final NavigationPane navigationPane;
  
  private VLayout panel; 
  private HLayout northLayout;  
  private HLayout southLayout;
  private VLayout westLayout;  
  
  @Inject
  public MainPageSmartGwtView(Masthead masthead, ApplicationMenu applicationMenu, 
      NavigationPaneHeader navigationPaneHeader, NavigationPane navigationPane) {
    this.masthead = masthead;
    this.applicationMenu = applicationMenu;
    this.navigationPaneHeader = navigationPaneHeader;
    this.navigationPane = navigationPane;
    
    // get rid of scroll bars, and clear out the window's built-in margin,
    // because we want to take advantage of the entire client area
    Window.enableScrolling(false);
    Window.setMargin(DEFAULT_MARGIN);    
    
    // initialise the main layout container
    panel = new VLayout();
    panel.setWidth100();  
    panel.setHeight100();  
    
    // initialise the North layout container
    northLayout = new HLayout();  
    northLayout.setHeight(NORTH_HEIGHT); 
 
    initApplicationMenu();
    
    // initialise the nested layout container
    VLayout vLayout = new VLayout(); 
    vLayout.addMember(this.masthead);
    vLayout.addMember(this.applicationMenu);
    vLayout.addMember(this.navigationPaneHeader); 
       
    // add the nested layout container to the North layout container
    northLayout.addMember(vLayout);
    
    initNavigationPane();
    
    // initialise the West layout container
    westLayout = this.navigationPane;
   
    // initialise the South layout container
    southLayout = new HLayout(); 
    
    // add the North and South layout containers to the main layout container
    panel.addMember(northLayout);  
    panel.addMember(southLayout);     
  }

  @Override
  public Widget asWidget() {
    return panel;
  }
  
  @Override
  public void setInSlot(Object slot, Widget content) {
    
    GWT.log("MainPageSmartGwtView.setInSlot()... ", null);
    
    if (slot == MainPageSmartGwtPresenter.TYPE_SetContextAreaContent) {
      if (content != null) {
        southLayout.setMembers(westLayout, (VLayout)content);  
      }
    } else {
      super.setInSlot(slot, content);
    }
  }  
  
  private void initApplicationMenu() {
    applicationMenu.addMenu(Serendipity.getConstants().NewActivityMenuName(), 
        DEFAULT_MENU_WIDTH, Serendipity.getConstants().NewActivityMenuItemNames(), 
        null);
    applicationMenu.addMenu(Serendipity.getConstants().NewRecordMenuName(), 
        DEFAULT_MENU_WIDTH, Serendipity.getConstants().NewRecordMenuItemNames(),
        null);
    Menu goToMenu = applicationMenu.addMenu(Serendipity.getConstants().GoToMenuName(), 
        DEFAULT_MENU_WIDTH - 30 );
    applicationMenu.addSubMenu(goToMenu, Serendipity.getConstants().SalesMenuItemName(), 
        Serendipity.getConstants().SalesMenuItemNames(),
        null);
    applicationMenu.addSubMenu(goToMenu, Serendipity.getConstants().SettingsMenuItemName(), 
        Serendipity.getConstants().SettingsMenuItemNames(),
        null);
    applicationMenu.addSubMenu(goToMenu, Serendipity.getConstants().ResourceCentreMenuItemName(),
        Serendipity.getConstants().ResourceCentreMenuItemNames(),
        null);
    applicationMenu.addMenu(Serendipity.getConstants().ToolsMenuName(), 
        DEFAULT_MENU_WIDTH - 30, Serendipity.getConstants().ToolsMenuItemNames(),
        null);
    applicationMenu.addMenu(Serendipity.getConstants().HelpMenuName(), 
        DEFAULT_MENU_WIDTH - 30, Serendipity.getConstants().HelpMenuItemNames(),
        null);    
  }
  
  private void initNavigationPane() {
    navigationPane.add(Serendipity.getConstants().SalesStackSectionName(), 
        SalesNavigationPaneSectionData.getRecords(), 
        SalesDataSource.getInstance());
    navigationPane.add(Serendipity.getConstants().SettingsStackSectionName(), 
        SettingsNavigationPaneSectionData.getRecords(), 
        SettingsDataSource.getInstance());
    navigationPane.add(Serendipity.getConstants().ResourceCentreStackSectionName(), 
        ResourceCentreNavigationPaneSectionData.getRecords(), 
        ResourceCentreDataSource.getInstance()); 
  }
  
  public NavigationPaneHeader getNavigationPaneHeader() {
    return navigationPaneHeader;
  }
  
  public NavigationPane getNavigationPane() {
    return navigationPane;
  }  
}

The corresponding Presenter class, MainPageSmartGwtPresenter:

...

public class MainPageSmartGwtPresenter extends 
    Presenter<MainPageSmartGwtPresenter.MyView, MainPageSmartGwtPresenter.MyProxy> {
        
  private final PlaceManager placeManager;
    
  @ProxyCodeSplit
  @NameToken(NameTokens.mainSmartGwtPage)
  public interface MyProxy extends Proxy<MainPageSmartGwtPresenter>, Place {
  }
    
  public interface MyView extends View {
    NavigationPaneHeader getNavigationPaneHeader();
    NavigationPane getNavigationPane();
  }
  
  /**
   * Use this in leaf presenters, inside their {@link #revealInParent} method.
   */
  @ContentSlot
  public static final Type<RevealContentHandler<?>> TYPE_SetContextAreaContent = new Type<RevealContentHandler<?>>();

  @Inject
  public MainPageSmartGwtPresenter(EventBus eventBus, MyView view, MyProxy proxy,
      PlaceManager placeManager) {
    super(eventBus, view, proxy);
    
    this.placeManager = placeManager;
  }

  @Override
  protected void onBind() {
    super.onBind();
    
    GWT.log("MainPageSmartGwtPresenter.onBind()... ", null);
    
    // register Handler's
    getView().getNavigationPane().addRecordClickHandler("Sales", new NavigationPaneClickHandler() );

    PlaceRequest myRequest = new PlaceRequest(NameTokens.activities);
    placeManager.revealPlace(myRequest); 
    
    getView().getNavigationPane().selectRecord(NameTokens.activities);
  }
  
  @Override
  protected void onReveal() {
    super.onReveal();
    
    // getView().getNavigationPane().selectRecord(NameTokens.activities);
  }  

  @Override
  protected void onReset() {
    super.onReset();
  }

  @Override
  protected void revealInParent() {
    RevealRootLayoutContentEvent.fire(this, this);
  }
  
  public class NavigationPaneClickHandler implements RecordClickHandler {
    @Override
    public void onRecordClick(RecordClickEvent event) {
      
      Record record = event.getRecord();  
      String name = record.getAttributeAsString("name");
      
      GWT.log("NavigationPaneClickHandler.onRecordClick() - " + name, null);
      
      PlaceRequest myRequest = new PlaceRequest(name); 
      
      placeManager.revealPlace(myRequest); 
      
      getView().getNavigationPaneHeader().setContextAreaHeaderLabelContents(name);
    }
  }   
}

And the PlaceManger class, SerendipityPlaceManager:

...

public class SerendipityPlaceManager extends PlaceManagerImpl {
  
  private final PlaceRequest defaultPlaceRequest;

  @Inject
  public SerendipityPlaceManager(EventBus eventBus, TokenFormatter tokenFormatter, 
      @DefaultPlace String defaultNameToken) {
    super(eventBus, tokenFormatter);
    
    this.defaultPlaceRequest = new PlaceRequest(defaultNameToken);
  }

  @Override
  public void revealDefaultPlace() {
    revealPlace(defaultPlaceRequest);
  }
  
  @Override
  public void revealErrorPlace(String invalidHistoryToken) {
    PlaceRequest myRequest = new PlaceRequest(NameTokens.errorPage);
    revealPlace(myRequest);     
  }  
}

6. Create an Activities page (nested Presenter and View) that uses smartGWT widgets

The third page we want to create is the Activities page, so we need to create a new View class, ActivitiesView:

...

public class ActivitiesView extends ViewImpl implements ActivitiesPresenter.MyView {
  
  private static final String CONTEXT_AREA_WIDTH = "*";  
  
  private final ToolBar toolbar;
  private final ActivitiesContextAreaListGrid listGrid;
  private final StatusBar statusBar;
 
  private VLayout panel; 
  
  @Inject
  public ActivitiesView(ToolBar toolbar, ActivitiesContextAreaListGrid listGrid, 
      StatusBar statusBar) {
    
    GWT.log("init ActivitiesView()...", null);
    
    this.toolbar = toolbar;
    this.listGrid = listGrid;
    this.statusBar = statusBar;
    
    panel = new VLayout();
    
    // initialise the Activities View layout container
    panel.addStyleName("crm-ContextArea");
    panel.setWidth(CONTEXT_AREA_WIDTH); 
    
    // add the Tool Bar, List Grid, and Status Bar to the Activities View layout container
    panel.addMember(this.toolbar);
    panel.addMember(this.listGrid);
    panel.addMember(this.statusBar);
    // this.addMember(this.jumpBar);    
  }

  @Override
  public Widget asWidget() {
    return panel;
  }
}

And the corresponding Presenter class, ActivitiesPresenter:

...

public class ActivitiesPresenter extends 
    Presenter<ActivitiesPresenter.MyView, ActivitiesPresenter.MyProxy> {
  
  private final PlaceManager placeManager;
  
  @ProxyCodeSplit
  @NameToken(NameTokens.activities)
  public interface MyProxy extends Proxy<ActivitiesPresenter>, Place {
  }
  
  public interface MyView extends View {
  }

  @Inject
  public ActivitiesPresenter(EventBus eventBus, MyView view, MyProxy proxy,
      PlaceManager placeManager) {
    super(eventBus, view, proxy);
    
    this.placeManager = placeManager;
  }

  @Override
  protected void onBind() {
    super.onBind();
  }

  @Override
  protected void onReset() {
    super.onReset();
  }

  @Override
  protected void revealInParent() {
    RevealContentEvent.fire(this, MainPageSmartGwtPresenter.TYPE_SetContextAreaContent, 
        this);
  }
}

The Main page (and the nested Activities presenter and view):

 

The Main page (and the nested Calendar presenter and view):

 

The Main page (and the nested Accounts presenter and view):

 

You may recall from the previous post that GIN requires us to create a method for each object type we want to create and then an implementation of the interface will be generated for us at compile time. So, we need to update the Ginjector class, SerendipityGinjector:

...

@GinModules({DispatchAsyncModule.class, ClientModule.class})
public interface SerendipityGinjector extends Ginjector {

  EventBus getEventBus();
  PlaceManager getPlaceManager();
  
  Provider<SignInPagePresenter> getSignInPagePresenter();
  
  AsyncProvider<ErrorPagePresenter> getErrorPagePresenter();
  AsyncProvider<MainPagePresenter> getMainPagePresenter();
  AsyncProvider<MainPageSmartGwtPresenter> getMainPageSmartGwtPresenter();
  AsyncProvider<ActivitiesPresenter> getActivitiesPresenter();
  AsyncProvider<CalendarPresenter> getCalendarPresenter();
  AsyncProvider<AccountsPresenter> getAccountsPresenter();
  
  ProxyFailureHandler getProxyFailureHandler();
}

We also need to bind the various classes and providers using a module. The AbstractPresenterModule class, ClientModule:

...

public class ClientModule extends AbstractPresenterModule {

  @Override
  protected void configure() {
    bind(EventBus.class).to(DefaultEventBus.class).in(Singleton.class);
    bind(PlaceManager.class).to(SerendipityPlaceManager.class).in(Singleton.class);
    bind(TokenFormatter.class).to(ParameterTokenFormatter.class).in(
        Singleton.class);
    bind(RootPresenter.class).asEagerSingleton();
    bind(ProxyFailureHandler.class).to(DefaultProxyFailureHandler.class).in(
        Singleton.class);
    
    // Constants
    bindConstant().annotatedWith(DefaultPlace.class).to(NameTokens.signInPage);

    // Presenters
    bindPresenter(ErrorPagePresenter.class, ErrorPagePresenter.MyView.class,
        ErrorPageView.class, ErrorPagePresenter.MyProxy.class);
    bindPresenter(SignInPagePresenter.class, SignInPagePresenter.MyView.class,
        SignInPageView.class, SignInPagePresenter.MyProxy.class);
    bindSingletonPresenterWidget(ErrorDialogPresenterWidget.class,
        ErrorDialogPresenterWidget.MyView.class, ErrorDialogPopupView.class);
    bindPresenter(MainPagePresenter.class, MainPagePresenter.MyView.class,
        MainPageView.class, MainPagePresenter.MyProxy.class);
    bindPresenter(MainPageSmartGwtPresenter.class, MainPageSmartGwtPresenter.MyView.class,
        MainPageSmartGwtView.class, MainPageSmartGwtPresenter.MyProxy.class);  
    
    // Sales
    bindPresenter(ActivitiesPresenter.class, ActivitiesPresenter.MyView.class,
        ActivitiesView.class, ActivitiesPresenter.MyProxy.class);  
    bindPresenter(CalendarPresenter.class, CalendarPresenter.MyView.class,
        CalendarView.class, CalendarPresenter.MyProxy.class);      
    bindPresenter(AccountsPresenter.class, AccountsPresenter.MyView.class,
        AccountsView.class, AccountsPresenter.MyProxy.class);      
  }	
}

The application's entry point

In the application's entry point class we need to create the ginjector, ensure everything is bound correctly, and reveal the current place in the URL:

...

public class Serendipity implements EntryPoint {

  interface GlobalResources extends ClientBundle {
    @NotStrict
    @Source("Serendipity.css")
    CssResource css();
  } 
  
  private static SerendipityConstants constants;  
  
  public final SerendipityGinjector ginjector = GWT.create(SerendipityGinjector.class);
  
  public void onModuleLoad() {
    
    GWT.log("init OnLoadModule()...", null);  
    
    // inject global styles
    GWT.<GlobalResources>create(GlobalResources.class).css().ensureInjected();    
    
    // load constants
    constants = (SerendipityConstants) GWT.create(SerendipityConstants.class);      
    
    // this is required by gwt-platform proxy's generator
    DelayedBindRegistry.bind(ginjector);
    
    ginjector.getPlaceManager().revealCurrentPlace();
  }  
  
  public static SerendipityConstants getConstants() {
    return constants;
  }   
}

6. Test the application in development mode

At this point, you should be able to compile the new sample application and launch it from within Eclipse:

 

Note: You can download the sample application from the crmdipity (Se-r-en-dipity) project's download page.

What's Next

At this point, we've created a sample application so that we could learn more about gwt-platform and smartGWT. Next we'll add a few more features to our sample application, Serendipity.

Working with gwt-platform and smartGWT - part 2

Comments

Great tutorial

Thanks for posting!
:)

Great posts

Hi,

Thanks for the posts re gwt-platform and SmartGwt.

Keep up the good work :-)

Regards
Chris

Loading

hello thanks for sharing, i have a bug when i try to run the serendipity, the page only stay in the loading and the following errors display the development mode, i download the source code from the google code page v. 0.3.0
00:00:29.908 [ERROR] Errors in 'file:/E:/Programacion/java/codigo/Serendipity/src/au/com/uptick/serendipity/client/sales/account/presenter/AccountInformationPresenter.java'
00:00:29.908 [ERROR] Line 77: No source code is available for type au.com.uptick.serendipity.shared.action.RetrieveAccountAction; did you forget to inherit a required module?
00:00:29.908 [ERROR] Line 78: No source code is available for type au.com.uptick.serendipity.shared.action.RetrieveAccountResult; did you forget to inherit a required module?
00:00:29.908 [ERROR] Errors in 'file:/E:/Programacion/java/codigo/Serendipity/src/au/com/uptick/serendipity/client/sales/presenter/AccountsPresenter.java'
00:00:29.908 [ERROR] Line 83: No source code is available for type au.com.uptick.serendipity.shared.action.RetrieveAccountsAction; did you forget to inherit a required module?
00:00:29.908 [ERROR] Line 84: No source code is available for type au.com.uptick.serendipity.shared.action.RetrieveAccountsResult; did you forget to inherit a required module?

Loading Serendipity

Hi,

Is there an issue with the setup of your environment?

Within Eclipse you should be able to right-click on Serendipity in the Package Explorer and then choose Run As -> Web Application.

Then choose "Serendipity.html" when prompted for the HTML Page Selection.

If you are still having problems please raise an issue -> http://code.google.com/p/crmdipity/issues/list

Cheers
Rob

GWT 2.4, GWTP 0.6 and smartGWT 2.5

Thanks a lot for these tutorials which have helped me to learn about gwt-platform. 

What do I need to do in order to migrate Serendipity to GWT 2.4, GWTP 0.6 and smartGWT 2.5?

Re: GWT 2.4, GWTP 0.6 and smartGWT 2.5

Hi,

Take a look at the GWTP porting page:

-> http://code.google.com/p/gwt-platform/wiki/PortingV1

and the gwt-cx pom.xml's:

-> http://code.google.com/p/gwt-cx/source/browse/trunk/gwtcx/pom.xml

Cheers
Rob