Request for comments on a custom framework

24 views
Skip to first unread message

Andrew Green

unread,
Nov 29, 2011, 4:32:40 PM11/29/11
to Google Web Toolkit, Andrew Green
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.

Alfredo Quiroga-Villamil

unread,
Nov 29, 2011, 4:40:54 PM11/29/11
to google-we...@googlegroups.com, Andrew Green
I like the idea. I specially like this "And we got rid of the infamous "if (place instanceof SomePlace)" chain in ActivityMapper!"

Ah and I also like the name of the project "pescador".

I will try to make some time to check this out.

Best of luck!

Alfredo 

--
You received this message because you are subscribed to the Google Groups "Google Web Toolkit" group.
To post to this group, send email to google-we...@googlegroups.com.
To unsubscribe from this group, send email to google-web-tool...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/google-web-toolkit?hl=en.



--
Alfredo Quiroga-Villamil

AOL/Yahoo/Gmail/MSN IM:  lawwton


Thomas Broyer

unread,
Nov 29, 2011, 6:50:47 PM11/29/11
to google-we...@googlegroups.com
I haven't looked at the code yet (not easy to read code on mobile) but I wonder how you manage the case where an activity can be used totally distinct places? In our project for instance, we show the same activity for an IllustrationPlace and a LegendPlace (a legend being the use of an illustration within a file): we show the information about the illustration in both cases.

Andrew Green

unread,
Nov 29, 2011, 10:56:41 PM11/29/11
to google-we...@googlegroups.com, Andrew Green
Hi...

El 29/11/11 17:50, Thomas Broyer escribió:
I haven't looked at the code yet (not easy to read code on mobile) but I wonder how you manage the case where an activity can be used totally distinct places? In our project for instance, we show the same activity for an IllustrationPlace and a LegendPlace (a legend being the use of an illustration within a file): we show the information about the illustration in both cases.
The way things are wired up right now, there are a couple options:

1) Set the activity's generic Place parameter to a common ancestor of the places where it'll appear.

This could be the abstract class WebClientPlace, which is the ancestor of all places used in the framework, or it could be some other common ancestor lower down on the class hierarchy. In this case your activity could look like this:
public class SomeCommonActivityImpl
        extends WebClientActivityBase<
        SomeCommonView,
        AncestorOfPlacesWhereThisActivityIsUsed,
        SomeComponentProvidingCommonFunctionality>
        implements SomeCommonActivity { 

	[...]
}
Then, in the components that set up activity/view/ui-region bindings for the specific places where you want to use this activity, you'd just inject in an appropriate activity factory and bind it to a ui region, just like you'd do for any other activity. So you could have any number of components more or less like this:
public class SpecificPAVComponentImpl extends PlaceActivityViewComponentBase<
        SpecificPAVComponent,
        SpecificPlace>
        implements
        SpecificPAVComponent {

    @Inject
    public SpecificPAVComponentImpl(
            SpecificPlaceProvider specificPlaceProvider,
            ActivitiesFactory<AncestorOfPlacesWhereThisActivityIsUsed, SomeCommonActivity>
                    someCommonActivitiesFactory,
	    [...]
            ) {

	    super([...]);

            addRegionAndActivitiesFactory(SomeUIRegion.class, someCommonActivitiesFactory);
            [...]

    }
}
Note that I haven't tried this yet, though I did think of this possibility, and in theory it would work.

Note also that if you do it this way, the return type of the getPlace() method available in your activity would be AncestorOfPlacesWhereThisActivityIsUsed. For cases where you want to deal with your place instance as something more specific, see option (2), below.

(Problems will arise if one needs to do this really a lot, and in many different ways, to the degree that the single-inheritance class hierarchy of Places becomes insufficient to express relevant commonalities and differences between places. Still, I'm sure the framework could be adapted to accommodate this---for example, rather than inheriting a common abstract Place class that inherits back to GWT's Place class, places could implement a common Place interface.)

2) Create an abstract class that provides the common functionality you need, and make specific activities that inherit it.

This does work in the framework as it stands, and it's what I'm using for common functionality in the banner region of our app. So I have a StandardBannerActivityBase class that can be inherited by other activities.

Many thanks again for your comments, suggestions, etc.!
- Andrew
Reply all
Reply to author
Forward
0 new messages