Step 1: Design layout for your app
Every app often begins with an idea + layout design. For example, we have an idea and a layout design as the following:
The layout has 3 areas: top area (may be used for app menu/toolbar), left area (may be used for app masters), right area (may be used for app main content/display).
It's very ugly layout design like Shrek but simple and easy to quick start in right way -:)
In this example, we have the details as below:
Top area: has 2 buttons:
+Hello World!: click on it >> change text of the button on left area to "Goodbye World!" and change content of right area to "Hello World!"
+ Hello Friend!: click on it >> change text of the button on left area to "Goodbye Friend!" and change content of right area to "Hello Friend !"
Left area: has 1 button, click on it >> change content of right area to "Thanks for your visiting. Goodbye!" and clear left area
Right area: display content depended on clicking on which buttons in top/left area
Step 2: Create a project to develop the app
You can use any tools provided by Google or others to manage the app. I used:
-Eclipse 3.6 Helios (http://www.eclipse.org/downloads)
-Google Plugin for GWT 2.1 and App Engine 1.3.8 (http://dl.google.com/eclipse/plugin/3.6)
-GWT Designer (http://dl.google.com/eclipse/inst/d2gwt/latest/3.6)
For example, we create a project name "hellomvp" with package name "com.hellomvp".
You can download this example at https://code.google.com/p/lvhung.
Step 3: Create OneWidgetLayoutPanel
We can use SimplePanel for this example, but I want to introduce a layout panel that we can use to add other LayoutPanels like DockLayoutPanel etc. It must implement AcceptsOneWidget for nesting. This class should be in com.hellomvp.client.ui package. See:
public class OneWidgetLayoutPanel extends LayoutPanel implements AcceptsOneWidget {Step 4: Create PresenterNavigator interface
private IsWidget widget = null;
@Override
public void setWidget(IsWidget w) {
if( widget != null) super.remove(widget);
widget = w;
if(w != null) super.add(w);
}
}
This interface will be used to navigate to places in views. It should be in com.hellomvp.client.ui package. See:
package com.hellomvp.client.ui;Step 5: Create Places
import com.google.gwt.place.shared.Place;
public interface PresenterNavigator {
void goTo(Place place);
}
A Place is tied with a particular state of the UI. An Activity will be called via URL by a corresponding Place. The app has 2 activities: hello and goodbye, so it needs 2 Places. They should be in com.hellomvp.client.place package. See:
public class HelloPlace extends Place {
private String helloName;
public HelloPlace(String token)
{
this.helloName = token;
}
public String getHelloName()
{
return helloName;
}
public static class Tokenizer implements PlaceTokenizer<HelloPlace>
{
@Override
public String getToken(HelloPlace place)
{
return place.getHelloName();
}
@Override
public HelloPlace getPlace(String token)
{
return new HelloPlace(token);
}
}
}
============================================Because we intend to not save URL state and handle URL history when user say Goodbye, so we will drop PlaceTokenizer in GoodbyePlace.
public class GoodbyePlace extends Place {
}
Step 6: Create PlaceHistoryMapper
This class will declares all available Places to manage URL history. It uses the annotation @WithTokenizers to list each of your tokenizer classes. We don't want to save GoodbyePlace URL state, so it won't be here. It should be in com.hellomvp.client.mapper package. See:
@WithTokenizers({ HelloPlace.Tokenizer.class })Step 7: Create views
public interface AppPlaceHistoryMapper extends PlaceHistoryMapper {
}
We will create views for areas in the layout. Firstly, we have EntryPoint class with UiBinder using DockLayoutPanel. In which, we use OneWidgetLayoutPanel for nesting Places on left and right area. See:
<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">An UI state in each area should have a view. These views should be in com.hellomvp.client.ui package. We will have:
<ui:UiBinder
xmlns:ui='urn:ui:com.google.gwt.uibinder'
xmlns:g='urn:import:com.google.gwt.user.client.ui'
xmlns:h='urn:import:com.hellomvp.client.ui' >
<g:DockLayoutPanel unit='EM'>
<g:north size='5'>
<h:TopPanel ui:field='topPanel' />
</g:north>
<g:center>
<h:OneWidgetLayoutPanel ui:field='leftPanel' >
</h:OneWidgetLayoutPanel>
</g:center>
<g:east size='50'>
<h:OneWidgetLayoutPanel ui:field='rightPanel' >
</h:OneWidgetLayoutPanel>
</g:east>
</g:DockLayoutPanel>
</ui:UiBinder>
-TopPanel (.java+.ui.xml) on top area
-HelloLeftPanel (.java+.ui.xml) for hello state on left area
-HelloRightPanel (.java+.ui.xml) for hello state on right area
-GoodbyeRightPanel (.java+.ui.xml) for goodbye state on right area
Step 8: Create ClientFactory
ClientFactory is very useful to get needed objects (such as EventBus) throughout your application. We also can change app UIs depended on some properties (like browser user agent). See:
public interface ClientFactory {Step 9: Modify module .gwt.xml file
EventBus getEventBus();
PlaceController getPlaceController();
HelloRightPanel getHelloRightPanel();
HelloLeftPanel getHelloLeftPanel();
GoodbyeRightPanel getGoodbyeRightPanel();
}
Now it's time to modify module .gwt.xml file (HelloMVP.gwt.xml). We add lines as the following:
<!-- Other module inherits -->We can change ClientFactoryImpl for GWT deferred binding here.
<inherits name="com.google.gwt.activity.Activity"/>
<inherits name="com.google.gwt.place.Place"/>
<!-- Use ClientFactoryImpl by default -->
<replace-with class="com.hellomvp.client.ClientFactoryImpl">
<when-type-is class="com.hellomvp.client.ClientFactory"/>
</replace-with>
Step 10: Create activities for each area
An Activity is analogous to a presenter in MVP terminology. Activities are started and stopped by an ActivityManager associated with a container Widget. ActivityManager can alert when user leave an activity. These Activities should be in com.hellomvp.client.activity package.
Every activity in every area should be defined. We have:
-LeftPanelHelloActivity for hello activity on left area
-LeftPanelGoodbyeActivity for goodbye activity on left area
-RightPanelHelloActivity for hello activity on right area
-RightPanelGoodbyeActivity for goodbye activity on right area
See LeftPanelHelloActivity and RightPanelHelloActivity:
public class LeftPanelHelloActivity extends AbstractActivity implements PresenterNavigator {Step 11: Create activity mappers
private ClientFactory clientFactory;
private String name; //name that will be appended to "Goodbye "
public LeftPanelHelloActivity(HelloPlace place, ClientFactory clientFactory) {
this.name = place.getHelloName();
this.clientFactory = clientFactory;
}
@Override
public void start(AcceptsOneWidget panel, EventBus eventBus) {
HelloLeftPanel helloLeftPanel = clientFactory.getHelloLeftPanel();
helloLeftPanel.setName(name);
helloLeftPanel.setNavigator(this);
panel.setWidget(helloLeftPanel.asWidget());
}
@Override
public void goTo(Place place) {
clientFactory.getPlaceController().goTo(place);
}
}
============================================
public class RightPanelHelloActivity extends AbstractActivity {
private ClientFactory clientFactory;
private String name; //name that will be appended to "Hello "
public RightPanelHelloActivity(HelloPlace place, ClientFactory clientFactory) {
this.name = place.getHelloName();
this.clientFactory = clientFactory;
}
/**
* Invoked by the ActivityManager to start a new Activity
*/
@Override
public void start(AcceptsOneWidget panel, EventBus eventBus) {
HelloRightPanel helloRightPanel = clientFactory.getHelloRightPanel();
helloRightPanel.setName(name);
panel.setWidget(helloRightPanel.asWidget());
}
/**
* Ask user before stopping this activity
*/
@Override
public String mayStop() {
return "Please hold on. This activity is stopping.";
}
}
Yeah, we have all Places and Activities. Now let map them together. GWT provides ActivityMapper interface for mapping each Place to its corresponding Activity. Every area should have an ActivityMapper. These ActivityMappers should be in com.hellomvp.client.mapper package.
We have 2 classes: LeftPanelActivityMapper and RightPanelActivityMapper. See:
public class RightPanelActivityMapper implements ActivityMapper {Step 12: Put them all together
private ClientFactory clientFactory;
public RightPanelActivityMapper(ClientFactory clientFactory) {
super();
this.clientFactory = clientFactory;
}
@Override
public Activity getActivity(Place place) {
if (place instanceof HelloPlace)
return new RightPanelHelloActivity((HelloPlace) place, clientFactory);
else if (place instanceof GoodbyePlace)
return new RightPanelGoodbyeActivity(clientFactory);
return null;
}
}
=================================================
public class LeftPanelActivityMapper implements ActivityMapper {
private ClientFactory clientFactory;
public LeftPanelActivityMapper(ClientFactory clientFactory) {
super();
this.clientFactory = clientFactory;
}
@Override
public Activity getActivity(Place place) {
if (place instanceof HelloPlace)
return new LeftPanelHelloActivity((HelloPlace) place, clientFactory);
else if (place instanceof GoodbyePlace)
return new LeftPanelGoodbyeActivity();
return null;
}
}
Finally, we will put them all together. We can use EntryPoint class. See:
public class HelloMVP implements EntryPoint
{
interface Binder extends UiBinder<DockLayoutPanel, HelloMVP> { }
private static final Binder binder = GWT.create(Binder.class);
private Place defaultPlace = new HelloPlace("World!");
@UiField TopPanel topPanel;
@UiField OneWidgetLayoutPanel leftPanel;
@UiField OneWidgetLayoutPanel rightPanel;
/**
* This is the entry point method.
*/
public void onModuleLoad() {
// Create app layout
DockLayoutPanel outer = binder.createAndBindUi(this);
// Create ClientFactory using deferred binding so we can replace with different
// impls in gwt.xml
ClientFactory clientFactory = GWT.create(ClientFactory.class);
EventBus eventBus = clientFactory.getEventBus();
PlaceController placeController = clientFactory.getPlaceController();
// Start ActivityManager for each area with its ActivityMapper
ActivityMapper leftPanelActivityMapper = new LeftPanelActivityMapper(clientFactory);
ActivityManager leftPanelActivityManager = new ActivityManager(leftPanelActivityMapper, eventBus);
leftPanelActivityManager.setDisplay(leftPanel);
ActivityMapper rightPanelActivityMapper = new RightPanelActivityMapper(clientFactory);
ActivityManager rightPanelActivityManager = new ActivityManager(rightPanelActivityMapper, eventBus);
rightPanelActivityManager.setDisplay(rightPanel);
// Start PlaceHistoryHandler with our PlaceHistoryMapper
AppPlaceHistoryMapper historyMapper= GWT.create(AppPlaceHistoryMapper.class);
PlaceHistoryHandler historyHandler = new PlaceHistoryHandler(historyMapper);
historyHandler.register(placeController, eventBus, defaultPlace);
// Set ClientFactory to main toolbar
topPanel.setClientFactory(clientFactory);
// Add app layout to RootPanel
//RootPanel.get().add(outer);
RootLayoutPanel.get().add(outer);
// Goes to place represented on URL or default place
historyHandler.handleCurrentHistory();
}
}