#summary UiBinder use cases = GWT UiBinder Use Cases = Ray Ryan This document provides various use cases for the use of the UiBinder, a service to generate Widget and DOM structures from XML markup, to be introduced with GWT 2.0. Unless otherwise noted, everything described here has been implemented. The samples here ignore binder's localization features. See UiBinderI18n. = Quick start = If just want to jump right in, take a peak at [http://code.google.com/p/google-web-toolkit/source/detail?r=6192 this patch]. It includes the work to change the venerable Mail sample to use UiBinder. = Background = There are problems with the declarative ui template service as it was [DeclarativeUi originally proposed] * A template-based UI must be instantiated via GWT.create(), causing an implementation detail to be visible as public api * Within a template, only widgets with a zero arg constructor can be used * CssResource and other ImmutableResourceBundle variants cannot be used * Template xml files are found by magical name matching conventions, and applying more than one xml template to a class is impossible In addressing these issues, we have talked about encouraging a proxy style of use (basically, use Composite to wrap whatever widget gets GWT.create()'d), but dislike the extra object creation implied. We also hope for a system that can choose to use innerHTML, cloning, or DOM assembly as makes sense per browser type. These shortcomings could be addressed by a combination of developer discipline (yuck) and perhaps the builder pattern, but we still found ourselves faced with the likelihood of hurried developers wrapping an unneeded, generated object. Emily hit upon the idea of the Configurator (here rechristened UiBinder). It’s like a factory, but responsible for filling in the fields of a Widget (or other object) that someone else instantiates, rather than instantiating one itself. This seems to offer all the benefits of a builder, with no concerns of extra object creation, and as a nice side effect avoids a lot of boilerplate. This document illustrates its application in various use cases. {{{ /** * Interface implemented by classes that generate DOM or Widget structures from * ui.xml template files, and which inject portions of the generated UI into the * fields of an owner. *

* The generated UiBinder implementation will be based on an xml file resource * in the same package as the owner class, with the same name and a "ui.xml" * suffix. For example, a UI owned by class {@code bar.baz.Foo} will be sought * in {@code /bar/baz/Foo.ui.xml}. (To use a different template file, put the * {@link UiTemplate} annotation on your UiBinder interface declaration to point * the code generator at it.) * * @param The type of the root object of the generated UI, typically a * subclass of {@link com.google.gwt.dom.client.Element} or * {@link com.google.gwt.user.client.ui.UIObject} * @param The type of the object that will own the generated UI */ public interface UiBinder { /** * Creates and returns the root object of the UI, and fills any fields of owner * tagged with {@link UiField}. * * @param owner the object whose {@literal @}UiField needs will be filled */ U createAndBindUi(O owner); } }}} = Hello World = Make a simple generated UI, with a named element, and without widgets. {{{

Hello, .
}}} {{{ public class HelloWorld extends UIObject { // Could extend Widget instead interface MyUiBinder extends UiBinder {} private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class); @UiField SpanElement nameSpan; public HelloWorld(String name) { // call to createAndBindUi sets this.nameSpan setElement(uiBinder.createAndBindUi(this)); nameSpan.setInnerText(name); } } // Use: Element helloWorld = new HelloWorld("World").getElement(); }}} = Hello Composite World = Make a simple widget-based UI {{{ Hello, . }}} {{{ public class HelloWidgetWorld extends Composite { interface MyUiBinder extends UiBinder {} private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class); @UiField ListBox listBox; public HelloWidgetWorld(String... names) { // sets listBox initWidget(uiBinder.createAndBindUi(this)); for (String name : names) { listBox.addItem(name); } } } // Use: HelloWidgetWorld helloWorld = new HelloWidgetWorld("able", "baker", "charlie"); }}} = HTML entities = XML doesn't understand entities like {{{ }}}. Templates that needs such characters have to define them. As a convenience, we provide a set of definitions that you can import by setting your DOCTYPE appropriately: {{{ }}} Note that the GWT compiler won't actually visit this url to fetch the file. There is a copy baked into it is used when that url is seen. However, your IDE may will fetch it. = Hello Stylish World = With the element, you can define the CSS for your UI right where you need it. {{{ .pretty { background-color: Skyblue; }
Hello, .
}}} A CssResource interface is generated for you, along with a ClientBundle. This means that the compiler will warn you if you misspell the class name when you try to use it (e.g. {style.prettty}). Also, your css class name will be obfuscated, and so protected from collision with like classnames in other css blocks--no more global CSS namespace! In fact, you can take advantage of this within a single template: {{{ .pretty { background-color: Skyblue; } .pretty { background-color: Orange; }
Hello, .
}}} Finally, you don't have to have your CSS inside your ui.xml file. Most real world projects will probably keep their CSS in a separate file: {{{
Hello, .
}}} = Programatic access to inline Styles = (*type='' doesn't allow for multiple interfaces. Needs thinking*) {{{ .redBox { background-color:pink; border: 1px solid red; } .enabled { color:black; } .disabled { color:gray; }
I'm a box. And I'm red.
}}} {{{ public class MyFoo extends Widget { interface MyStyle extends CssResource { String enabled(); String disabled(); } @UiField MyStyle style; /* ... */ void setEnabled(boolean enabled) { getElement().addStyle(enabled ? : style.enabled() : style.disabled()); getElement().removeStyle(enabled ? : style.disabled() : style.enabled()); } } }}} =Simple binding of event handlers= (* Should the value argument ("button") should be optional? If so the handler should get all clickEvents if it is not provided. But what does "all" mean, exactly? For every named field? *) (* Cannot yet bind to non-widgets *) {{{ public class MyFoo extends Widget { @UiField Button button; /* ... */ @UiHandler("button") void onClick(ClickEvent e) { Window.alert("Hello, AJAX"); } }}} =Using an external resource with a UiBinder= {{{
Well hello there
}}} {{{ public class LogoNamePanel extends Composite { interface MyUiBinder extend UiBinder {} private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class); @UiField SpanElement userNameField; public LogoNamePanel() { initWidget(uiBinder.createAndBindUi(this)); } public void setUserName(String userName) { userNameField.setInnerText(userName); } } public interface Resources extends ClientBundle { @Resource("Style.css") Style style(); @Resource("Logo.jpg") ImageResource logoImage(); public interface Style extends CssResource { String mainBlock(); String nameSpan(); Sprite userPictureSprite(); } } }}} The with element declares a field holding an object whose methods can be called to fill in attribute values. If no public api is provided to set the "with" argument (as in this example), it must be instantiable by GWT.create(). Note that there is no requirement that a ui:with resource implement the ClientBundle interface, this is just an example. =Share resource instances= Extends LogoNamePanel (from the example above) to allow the resource to be passed in. {{{ public class LogoNamePanel extends Composite { interface MyUiBinder extend UiBinder {} private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class); @UiField SpanElement nameSpan; final Resources resources; public LogoNamePanel(Resources resources) { this.resources = resources; initWidget(uiBinder.createAndBindUi(this)); } public void setUserName(String userName) { nameSpan.setInnerText(userName); } @UiFactory /* this method could be static */ public Resources getResources() { return resources; } } }}} This can be even more concise: {{{ public class LogoNamePanel extends Composite { interface MyUiBinder extend UiBinder {} private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class); @UiField SpanElement nameSpan; @UiField(provided = true) final Resources resources; public LogoNamePanel(Resources resources) { initWidget(uiBinder.createAndBindUi(this)); this.resources = resources; } public void setUserName(String userName) { nameSpan.setInnerText(userName); } } }}} =Using a widget that requires constructor args= You have an existing widget that needs constructor arguments. {{{ public CricketScores(String... teamNames) {...} }}} You use it in a template. {{{ }}} {{{ public class UserDashboard extends Composite { interface MyUiBinder extends UiBinder {} private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class); public UserDashboard() { initWidget(uiBinder.createAndBindUi(this)); } } }}} An error results: *(line numbers not yet implemented)* {{{ UserDashboard.ui.xml:7:2 [ERROR] com.my.app.widgets.CricketScores has no default (zero args) constructor. You can define a @UiFactory annotated method on UserDashboard to create an instance; mark a CrickectScores field of UserDashboard with @UiField(provided=true) and put an instance there; or annotate a constructor of CricketScores with @UiConstructor to allow its arguments to be provided by the template. }}} So you either make the @UiFactory method: {{{ public class UserDashboard extends Composite { interface MyUiBinder extends UiBinder; private static final MyUiBinder uiBinder = GWT.create(MyUiBinder.class); private final String[] teamNames; public UserDashboard(String... teamNames) { this.teamNames = teamNames; initWidget(uiBinder.createAndBindUi(this)); } /** Used by MyUiBinder to instantiate CricketScores */ @UiFactory CricketScores makeCricketScores() { // method name is insignificant return new CricketScores(teamNames); } } }}} or perhaps: {{{ public class UserDashboard extends Composite { interface MyUiBinder extends UiBinder; private static final MyUiBinder uiBinder = GWT.create(MyUiBinder.class); public UserDashboard() { this.teamNames = teamNames; initWidget(uiBinder.createAndBindUi(this)); } /** * Used by MyUiBinder to instantiate CricketScores. * Arguments to be filled in the template */ @UiFactory CricketScores makeCricketScores(String... teamNames) { return new CricketScores(teamNames); } } }}} {{{ }}} or annotate the constructor: {{{ public @UiConstructor CricketScores(String... teamNames) {...} }}} {{{ }}} or fill in a field marked with @UiField(provided=true): {{{ public class UserDashboard extends Composite { interface MyUiBinder extends UiBinder; private static final MyUiBinder uiBinder = GWT.create(MyUiBinder.class); @UiField(provided=true) final CricketScores cricketScores; // cannot be private public UserDashboard(CricketScores cricketScores) { // DI fans take note! this.cricketScores = cricketScores; initWidget(uiBinder.createAndBindUi(this)); } } }}} =Apply different xml templates to the same widget= You're an [http://code.google.com/events/io/sessions/GoogleWebToolkitBestPractices.html MVP] developer. You have a nice view interface, and a templated Widget that implements it. How might you use several different xml templates for the same view? {{{ public class FooPickerController { public interface Display { HasText getTitleField(); SourcesChangeEvents getPickerSelect(); } public void setDisplay(FooPickerDisplay display) { ... } } public class FooPickerDisplay extends Composite implements FooPickerController.Display { @UiTemplate("RedFooPicker.ui.xml") interface RedBinder extends UiBinder {} private static RedBinder redBinder = GWT.create(MyUiBinder.class); @UiTemplate("BlueFooPicker.ui.xml") interface BlueBinder extends UiBinder {} private static BlueBinder blueBinder = GWT.create(MyUiBinder.class); @UiField HasText titleField; @UiField SourcesChangeEvents pickerSelect; public HasText getTitleField() { return titleField; } public SourcesChangeEvents getPickerSelect() { return pickerSelect; } protected FooPickerDisplay(UiBinder binder) { initWidget(binder.createAndBindUi(this)); } public static FooPickerDisplay createRedPicker() { return new FooPickerDisplay(redBinder); } public static FooPickerDisplay createBluePicker() { return new FooPickerDisplay(blueBinder); } } }}}