At Just Software we are building our software Just Connect using Google’s awesome Web Toolkit which lets us build the software almost entirely in Java, even the Web UI. For the layout part we’re building templates using the UiBinder framework of GWT which lets you build templates in XML and fill them with data from the Java class backing it (called the owner class).
Today I was faced with the task of building a GWT widget which has rather an amount of logic in it and has a different layout depending on a boolean value. I want to make this a little more clear to you with this screenshot which shows a microblog message and a list of comments attached to it:
As you can see the two comments are ordered by date in ascending order, so the newest comment sits at the bottom. This is a good thing since in a microblog the users want to read comments from top to bottom.
Now comes the challenge: We are using this same widget - which shows comments attached to something - in some other places of the software (e.g. in documents which you can comment, too). But the difference is that in these cases we want to show the newest comment at the top so the list of comments must be reversed. And not only this but the “write a comment” textarea and the “show NN further comments” link must also be exchanged so that the textarea sits at the top (newest comment above) and the link must sit at the bottom of the list.
How do we do that using UiBinder templates and without duplicating the code sitting in our Java owner class (which is among other things responsible for fetching the comments asynchronously and filling the template with the data)? I came up with the following solution: Pass a boolean parameter (named newestFirst) to the constructor of the widget and, depending on the value of the parameter, choose one of two different layout templates. This looks something like this:
public class CommentListWidget extends Composite {
...
@UiTemplate("templates/CommentListWidget.ui.xml")
interface CommentListWidgetUiBinder extends UiBinder<HTMLPanel, CommentListWidget> {};
@UiTemplate("templates/CommentListWidgetNewestFirst.ui.xml")
interface CommentListWidgetNewestFirstUiBinder extends UiBinder<HTMLPanel, CommentListWidget> {};
private final UiBinder<HTMLPanel, CommentListWidget> _binder;
public CommentListWidget(final boolean newestFirst) {
if(_newestFirst) {
_binder = GWT.create(CommentListWidgetNewestFirstUiBinder.class);
} else {
_binder = GWT.create(CommentListWidgetUiBinder.class);
}
initWidget(_binder.createAndBindUi(this));
}
...
}
The trick here is that we actually choose a different .ui.xml file depending on the value of newestFirst. Okay, that’s not really THE trick. What makes this a little dodgy is the fact that I want to use this widget inside of other UiBinder templates files. Per default you can only include widgets in UiBinder templates when they have a default (i.e. empty) constructor. The good thing is: We have a little friend to circumvent this limitation, it’s called @UiConstructor
. Annotate the above constructor with it and you may pass the parameter newestFirst as a simple parameter to your UiBinder template:
<prefix:CommentListWidget newestFirst="true" />
There’s one little downside to using @UiConstructor
: If you include the annotation in your owner class you cannot have a default constructor anymore. So if you decide to introduce a paramaterized constructor to a widget afterwards you’ll have to adapt all your code which uses this widget inside UiBinder templates. Using multiple @UiConstructor
annotations is also not permitted unfortunately.
Conclusion: The above pattern helps you building separate templates for different purposes without duplicating the code of the owner class. Comments and improvements welcome!