Tuesday, November 29, 2011

Request for comments on a custom framework

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.

No comments:

Post a Comment