This page contains a general proposal of how to define GWT widgets for the Geomajas GWT Client 2. The rules laid out here should cover map controls as well as any other widget. Our goal is to follow the GWT best practices as much as possible, while keeping usability in mind.
Defining a widget is not unlike defining any other API: it should be clear what it is and how to use it. Additional requirements for widgets is that their layout should be easily customized. We're not talking a few colors here and there, but the entire HTML construct of a widget should be customizable as well as it's CSS.
In short the following requirements should be met:
- All widgets that are defined in plugins should be part of the API (@Api annotation). We should never create widgets that are not meant to be used.
- The style of a widget should be easily customized. The more ways there are to customize the style, the better.
- The HTML construct of a widget should be customizable. This means we need a clean separation between the GUI definition and the actual code (MVC, MVP, ...)
- We should follow GWT best practices as much as possible. This should make it easier for others to understand our code.
- All widgets should support i18n.
Most of the above requirement can be met by making use of the GWT ClientBundles as much as possible:
- By using Messages or Constants the widgets should support i18n.
- By using ResourceBundles and CssResourceBundles we can provide a way for people to overwrite the default style. This only works though if the resource bundles are not initialized in a static object. Resource bundles should instead be provided through a constructor. It would be best to declare resources in a widget as final.
- By using the GWT UiBinder mechanism, one can provide a HTML construct for a widget. As with the normal resource bundles, the UiBinder instance should not be defined as a static, but as a final object. It should be provided through the constructors.
For every widget, we should define a specific resource bundle. In case some styles or images are often used, a CommonResource bundle should exist as well (could be per plugin). Still not every developer may want to make use of resource bundles to overwrite a widgets style. An an extra effort we should make sure that all CSS classes used in resource bundles have a fixed name. By disabling the CSS classname obfuscation, people can still provide their own CSS file the old-fashioned way.
For most widgets this would mean that at least the following files should be provided:
- Widget class (.java)
- Default UiBinder construct (.ui.xml)
- Resource bundle combining images and CSS.
- Css Resource bundle
- Css file
I would propose we create a package per widget and place all the above files in that package save for the images. For images I would create an "image" sub-folder.
Another important aspect is that for every widget we should provide a specific example in the showcase;
To separate the view from the presentation logic, the MVP pattern should be used whenever it makes sense (a sufficient amount of testable logic in the presenter). This pattern has some advantages:
- the view can be replaced by an entirely different implementation (even in another technology like SmartGWT)
- the view becomes easier to maintain as its code can be run separately (you can even make an entry point that shows all the views of your application)
- by replacing the view with a mock, the code becomes unit testable
Let's say we want to create a new widget by the name of "ExampleWidget". In that case, the other files should use the following names:
Using the ExampleWidget above, it's code would look something like this:
As you can see, the widget contains a view and a presenter, encapsulating the MVP implementation from the framework user. The view is by default obtained from a factory (see further), allowing easy replacement by the framework user.
The view is passed to the constructor of the presenter and the
IsWidget()implementation delegates to the view.
The view code (interface and implementation) would look something like this:
Note that the resource bundle or the UiBinder do not have to be declared as final objects. If you do not need them in any of your methods, just use them in the constructor without saving them to a field.
The presenter is passed to the view as a callback interface. The view is essentially passive and should only delegate actions to the presenter.
The client resource bundle is by default obtained from a factory (see further), allowing easy replacement by the framework user.
The presenter code (interface and implementation) would look something like this:
The presenter implementation takes the view as a constructor argument. The same constructor can be used for unit testing. The view interface has a setPresenter() method, which is called by the presenter in its constructor to connect the view with the presenter.
Factories and common plugin code
We define singleton factories for the client resource bundles and the views of all widgets. Singletons are obtained from the main plugin singleton, which has the plugin's name but is not its entry point.
If the plugin's name is example, the following files should be defined (in the top client package):
- Example.java (main singleton)
The main singleton would look as follows:
The factories can be replaced through deferred binding
The factories look like this:
To allow overriding of the style, a version of the client bundle has to be created that has empty css classes. The following files are involved:
ExampleWidgetResource but refers to example-widget-nostyle.css, which is the same as example-widget.css, but with empty css classes. The end result is an application with empty css classes baked in, allowing web developers to override the css in the normal way.
Unit testing the presenter is easy because the view can be mocked. We use the GWT mockito framework for this (see https://github.com/google/gwtmockito).
For our example, a unit test would look as follows:
The mockito framework allows this and much more:
- mocking of classes and interfaces
- specifying return values for mocked methods
- capturing arguments of mocked methods (useful if you want to mock a callback or make a more complex argument verification
- verifying the mocked methods are called with the correct arguments (using matchers)
Replacing the default style through GWT
To replace the default style, define another implementation for the client resource bundle factory through deferred binding:
MyExampleClientBundleFactory should implement the same methods but use different css files and images.
Replacing the default style through css
To replace the default style through css, define the nostyle implementation for the client resource bundle factory through deferred binding:
ExampleClientBundleFactoryNoStyle is an implementation that provides empty css classes for all the css used (as we explained in the no-style paragraph). By importing a css file at the web page level, the web developer can now define his or her own style for each of these classes. We could already provide a css file with all the default classes for this purpose, so the web developer can use the default style as a starting point.
Replacing the view completely
To replace the default style completely, define another implementation for the view factory through deferred binding:
A github project with sample code has been set up:
To test, clone the repo and run the usual in the example directory:
mvn install jetty:run