Friday, June 18, 2010

The Power of Declarative Code

The best policy is to declare victory and leave.
—George D. Aiken




While learning to write some basic UI using GWT, it is hard not to be aware of the work they are doing with UiBinder. At a very high level, they have introduced XML templates for a declarative syntax to build an interface. The basic example from the main page is:


<!-- HelloWorld.ui.xml -->

<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'>
<div>
Hello, <span ui:field='nameSpan'/>.
</div>
</ui:UiBinder>


From their example, they hook this up to a sample piece of Java code as:


public class HelloWorld extends UIObject { // Could extend Widget instead
interface MyUiBinder extends UiBinder<DivElement, HelloWorld> {}
private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class);

@UiField SpanElement nameSpan;

public HelloWorld() {
// createAndBindUi initializes this.nameSpan
setElement(uiBinder.createAndBindUi(this));
}

public void setName(String name) { nameSpan.setInnerText(name); }
}



It is not hard to see the advantage of this. Especially in a shop where you have designers who provide prototypes or templates in an XML format.

However, for those of us that are on the hook for everything individually, I contend that the annotation and XML approach are actually less appealing than an approach that can encode everything in one file. To that end, I have started using some helper methods to try and capture the essence of what UiBinder can accomplish for you.

My first target is what I consider to be the most appealing part of UiBinder, the declarative nature of the code. I am of the opinion that it would be incredibly dreadful if one had to drop to XML in order to get declarative code. Luckily, this is not the case. It is relatively easy to generate the above hello world example in a single file that shows the structure just as well:


public class HelloWorld extends UIObject {

SpanElement nameSpan = span();

public HelloWorld() {
getElement().appendChild(
div(
text("Hello, "), nameSpan, text(".")
)
);
}

public void setName(String name) { nameSpan.setInnerText(name); }
}


The above code can be added to a document in the same manner that the original example could. While not an extreme example, this still shows that two files, one at about 13 lines of code and one with 7, can be combined to a single 14 line snippet. And this ignores all of the setup one has to do to have UiBinder actually compile and run the code.

Of course, this wouldn't be that appealing if it did not work with Widgets. To that end, another set of helper classes and methods can give us:


public class PanelExample extends Composite {

WidgetConfig<Label> topLabel = label("").addStyle("title");

public PanelExample() {
this.initWidget(
dockPanel(
north(topLabel),
center(label("Body")),
west(
html(
orderedList(
item(text("Sidebar")),
item(text("Sidebar")),
item(text("Sidebar"))
)
)
)
)
);
}

public void setTopLabel(String text) {
topLabel.get().setText(text);
}
}


Essentially, just by taking advantage of some chained method calls (with a judicious use of varargs) and a simple wrapper object, we can create a section of Java code that has the same declarative qualities of XML, without having to resort to XML. (We also save ourselves the joy of seeing new sprinkled throughout our code.)

Further, we can take advantage of other private methods to help make other sections more readable. Consider, the above example could be recoded to look like:


public class PanelExample extends Composite {

WidgetConfig<Label> topLabel = label("").addStyle("title");

public PanelExample() {
this.initWidget(
dockPanel(
north(topLabel),
center(mainBody()),
west(sideBar())
)
);
}

private WidgetConfig<?> mainBody() {
return label("Body");
}

private WidgetConfig<?> sideBar() {
return html(
orderedList(
item(text("Sidebar")),
item(text("Sidebar")),
item(text("Sidebar"))
)
);
}

public void setTopLabel(String text) {
topLabel.get().setText(text);
}
}



This is definitely doable with tools such as UiBinder, but extracting out inner templates simply by using familiar IDE tools for refactoring is hard to scoff at.

There are, of course, several other good reasons to use UiBinder. The first is the way it treats styles. The second is the way in which it eliminates a lot of the code to attach listener objects to the view. I am currently working on some code to address each of these issues. I welcome any feedback. In particular, if I am incredibly misguided in these efforts, it is usually best to find out sooner, rather than later. :)

No comments: