Hi, all,
Like many other projects, our GWT app needed a general framework. We
opted to create one, and since I haven't seen anything else that
works exactly like ours, I thought I'd share what we've done, in the
hopes that doing so might contribute something useful to the
(already extensive) discussions on GWT MVP frameworks. We'd also
love to hear any comments/criticisms/suggestions you may have...!
In a nutshell:
- The framework is component-based. (No hot-swapping of components,
though---code must be recompiled in order to switch components in or
out.)
- Almost all objects are created in an injected context using GIN.
- We extend the standard GWT Place, Activity and ActivityManager
classes/interfaces (among others).
- There are two kinds of components in the framework: those that
provide activity/view/ui-region bindings for specific places, and
those that provide other kinds of functionality.
- Activities are a generified extension of GWT's Activity; Activity
classes have generic parameters that associate them with a specific
Place class, a View class, and a class that represents the component
they're a part of.
I'm not sure there's less boilerplate than in other MVP approaches.
At least, though, it seems like everything is together that goes
together, and we've got a decent, though very basic, path for
encapsulating functionality in components as we complete and extend
our app. And we got rid of the infamous "if (place instanceof
SomePlace)" chain in ActivityMapper!
Anyone who'd like to check it out can get it like this:
$ git clone
http://lais.mora.edu.mx/gitrepo/pescador.git/
$ cd webclient (note: other subdirectories of the repository
have unrelated stuff that you'll probably want to ignore)
Following are a few code snippets.
Here's part of a simple activity:
public class BodyStartPageActivityImpl
extends WebClientActivityBase<BodyStartPageView, StartPagePlace, StartPagePAVComponent>
implements BodyStartPageActivity {
@Inject
public BodyStartPageActivityImpl(
@Assisted StartPagePlace place) {
super(place);
}
@Override
public void start(AcceptsOneWidget container, EventBus eventBus) {
BodyStartPageView view = getView();
view.setText("Body start page activity here...<br />Another beautious line of start page.");
container.setWidget(view);
}
[...]
}
As shown, the activity is created using GIN, so it can have almost
anything injected via its constructor. It also gets access to the
correct view and place instances.
Here's a bit of a component that sets up activity/view/ui-region
bindings for a place:
public class ContentPAVComponentImpl extends PlaceActivityViewComponentBase<
ContentPAVComponent,
ContentPlace>
implements
ContentPAVComponent {
@Inject
public ContentPAVComponentImpl(
ContentPlaceProvider contentPlaceProvider,
ActivitiesFactory<ContentPlace, HeadContentActivity>
headActivitiesFactory,
ActivitiesFactory<ContentPlace, BodyContentActivity>
bodyActivitiesFactory,
ActivitiesFactory<ContentPlace, BannerContentActivity>
bannerActivitiesFactory,
ActivitiesFactory<ContentPlace, WestContentActivity>
westActivitiesFactory) {
super(
ContentPAVComponent.class,
"contenido",
contentPlaceProvider,
ContentPlace.class);
// set up regions and activities factories
addRegionAndActivitiesFactory(Head.class, headActivitiesFactory);
addRegionAndActivitiesFactory(Body.class, bodyActivitiesFactory);
addRegionAndActivitiesFactory(Banner.class, bannerActivitiesFactory);
addRegionAndActivitiesFactory(West.class, westActivitiesFactory);
}
public static class ContentGinModule extends AbstractGinModule {
// GIN module for bindings specific to this component
[...]
}
[...]
}
The component has a place provider and (GIN-generated) activity
factories injected, which are passed along to the superclass. In
turn, the superclass and other parts of the framework take care of
starting the appropriate activities and views in the appropriate ui
regions when the app goes to a place of the specified class. GIN
bindings specific to this component are set up in the component's
own GIN module.
Finally, here's a bit of a ComponetSetup class, which ties
everything together:
public class ActiveComponentSetup extends ComponentSetup {
/**
* Activate all necessary {@link GinModule}s for DI in the components to use,
* as well as for basic infrastructure and local GinModule.
*/
@GinModules({
ActiveComponentSetupGinModule.class, // current setup module, included below
WebClientGinModule.class, // required basic infrastructure
StandardDispatchModule.class, // required basic infrastructure
ContentGinModule.class, // content component
StartPageGinModule.class // start page component
})
public interface ActiveComponentSetupGinjector extends WebClientGinjector {}
/**
* <p>Sets specific bindings;
* Backreferences (from components back to {@link ActiveComponentSetup})
* are set, as necessary, as components are received.</p>
*
* <p>Also set the root region provider.</p>
*
* <p>Note: components that rely on automatic generation of internationalized
* {@link Messages} by Maven must also set up the appropriate configuration in the
* pom.xml.</p>
*/
@Inject
public ActiveComponentSetup(
WindowLayout winLayout, // global window layout widget
ContentPAVComponent contentPAVBinding, // general content component
StartPagePAVComponent startPagePAVBinding // start page component
) {
super();
// do this before registering components, so that PAVBinding components
// can be checked against the regions available here
setRootRegionsWidget(winLayout);
// register components
// TODO once multibindings and mapbindings are in Gin, look into using that
addComponents(contentPAVBinding, startPagePAVBinding);
setDefaultPlaceProvider(startPagePAVBinding);
}
[...]
}
So, basically, we have our Ginjector which brings together all the
required GIN modules. ComponentSetup's constructor gets via
injection the components we'll activate, and sets the default place
and the object that'll take care of global UI layout.
So at the entry point, all we have to do is this:
public class WebClient implements EntryPoint {
/**
* This is the entry point method.
*/
public void onModuleLoad() {
WebClientGinjector ginjector =
GWT.create(ActiveComponentSetupGinjector.class);
ginjector.getComponetSetup().start();
}
}
And that's that! Many thanks in advance for your comments,
suggestions, scathing criticisms, etc.!
Take care,
Andrew
P.S. It's all GPL, so anyone is welcome to reuse this, too.