Revision: 6125
Author: [email protected]
Date: Fri Sep 11 16:12:54 2009
Log: Moved here from Incubator
http://code.google.com/p/google-web-toolkit/source/detail?r=6125
Added:
/wiki/UiBinder.wiki
=======================================
--- /dev/null
+++ /wiki/UiBinder.wiki Fri Sep 11 16:12:54 2009
@@ -0,0 +1,513 @@
+#summary UiBinder use cases
+
+= GWT UiBinder Use Cases =
+
+Ray Ryan
+
+This document provides various use cases for the use of the UiBinder, a
proposed service to generate Widget and DOM structures from XML markup.
+
+The samples here ignore binder's localization features. See UiBinderI18n.
+
+= 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.
+ * <p>
+ * 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 <U> 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 <O> The type of the object that will own the generated UI
+ */
+public interface UiBinder<U, O> {
+
+ /**
+ * 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.
+
+{{{
+<!-- HelloWorld.ui.xml -->
+
+<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'>
+ <div>
+ Hello, <span ui:field='nameSpan'/>.
+ </div>
+</ui:UiBinder>
+}}}
+
+{{{
+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(String name) {
+ // call to createAndBindUi sets this.nameSpan
+ setElement(uiBinder.createAndBindUi(this));
+ nameSpan.setInnerText(name);
+ }
+}
+
+// Use:
+
+SpanElement helloWorld = new HelloWorld("World").getElement();
+}}}
+
+= Hello Composite World =
+
+Make a simple widget-based UI
+
+{{{
+<!-- HelloWidgetWorld.ui.xml -->
+
+<ui:UiBinder
+ xmlns:ui='urn:ui:com.google.gwt.uibinder'
+ xmlns:g='urn:import:com.google.gwt.user.client.ui'>
+ <g:HTMLPanel>
+ Hello, <g:ListBox ui:field='listBox'/>.
+ </g:HTMLPanel>
+</ui:UiBinder>
+}}}
+
+{{{
+public class HelloWidgetWorld extends Composite {
+
+ interface MyUiBinder extends UiBinder<Widget, HelloWidgetWorld> {}
+ 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");
+}}}
+
+=Putting a label on a checkbox (referring to generated ids within a
template)=
+
+_Not yet implemented._ You want to make your personal variant on the
single most common widget, a checkbox with a nice, accessible HTML label
element tied to it:
+
+{{{
+<!-- LabeledCheckBox.ui.xml -->
+
+<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'>
+ <span>
+ <input type='checkbox' ui:field='myCheckBox'>
+ <label ui:for='myCheckBox' ui:field='myLabel'/>
+ </span>
+</ui>
+}}}
+
+{{{
+public class LabeledCheckBox extends Widget {
+ interface MyUiBinder extends UiBinder<Widget, LabeledCheckbox> {}
+ private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class);
+
+ @UiField InputElement myCheckBox;
+ @UiField LabelElement myLabel;
+
+ public LabeledCheckBox() { setElement(uiBinder.createAndBindUi(this)); }
+
+ public void setValue(boolean b) { myCheckBox.setChecked(b); }
+
+ public boolean getValue() { return myCheckBox.isChecked(); }
+
+ public void setName(String name) { myLabel.setInnerText(name); }
+
+ public String getName() { return myLabel.getInnerText(); }
+}
+}}}
+
+The proposal here is that a ui: prefix on any attribute other than id
fills it with the id generated for a corresponding ui:field.
+
+There are type matching issues here. The ui:field of a DOM element is a
string id, while that for a UIObject is typed. So, this should fail with a
type mismatch:
+
+{{{
+<some:WidgetOfSomeKind ui:field='theWidget'> <label g:for='theWidget' />
+}}}
+
+<blockquote>
+ The use of attribute prefixing for this would be a mistake, a bad
+ use of XML. In particular, it fights the use of XML tools to enforce
+ things like, "all labels must have a 'for' attribute."
+
+ Instead, we should bite the bullet and adopt a mini-expression
+ language, something like:
+
+{{{
+<ui:UiBinder
+ xmlns:ui='urn:ui:com.google.gwt.uibinder'>
+ <span>
+ <input type='checkbox' ui:field='myCheckBox'>
+ <label for='{myCheckBox}' ui:field='myLabel'/>
+ </span>
+</ui:UiBinder>
+}}}
+
+</blockquote>
+
+=Using an ImmutableResourceBundle (e.g. CssResource) with a UiBinder=
+
+{{{
+<!-- LogoNamePanel.ui.xml -->
+<ui:UiBinder
+ xmlns:ui='urn:ui:com.google.gwt.uibinder'
+ xmlns:g='urn:import:com.google.gwt.user.client.ui'
+ xmlns:res='urn:with:com.my.app.widgets.logoname.Resources'>
+ <g:HTMLPanel>
+
+ <img res:apply='logoImage'>
+
+ <div res:class='style.mainBlock'>
+ <div res:apply='style.userPictureSprite'>
+ Well hello there
+ <span res:class='style.nameSpan' ui:field='userNameField'/>
+ </div>
+ </div>
+ </g:HTMLPanel>
+</ui:UiBinder>
+}}}
+
+{{{
+public class LogoNamePanel extends Composite {
+ interface MyUiBinder extend UiBinder<Widget, LogoNamePanel> {}
+ private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class);
+
+ @UiField SpanElement nameSpan;
+
+ public LogoNamePanel() {
+ initWidget(uiBinder.createAndBindUi(this));
+ }
+
+ public void setUserName(String userName) {
+ nameSpan.setInnerText(userName);
+ }
+}
+
+public interface Resources extends ClientBundle {
+ @Resource("Style.css")
+ Style style();
+
+ @Resource("Logo.jpg")
+ ImageResource widgetyImage();
+
+ public interface Style extends CssResource {
+ String mainBlock();
+ String nameSpan();
+ Sprite userPictureSprite();
+ }
+}
+}}}
+
+The with: uri type marks 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().
+
+An element can be passed as an argument to a method on such resource
+class via an apply attribute, as illustrated above with the Sprite and
+ImageResource uses.
+
+Note that there is no requirement that a with: class implement the
+ClientBundle interface.
+
+<blockquote>
+ As above, this is an abuse of attribute prefixing, and a bad idea.
+ Here again we should use our little expression language:
+
+{{{
+<ui:UiBinder
+ xmlns:ui='urn:ui:com.google.gwt.uibinder'
+ xmlns:g='urn:importcom.google.gwt.user.client.ui'
+ xmlns:res='urn:with:com.my.app.widgets.logoname.Resources'>
+
+ <g:HTMLPanel>
+
+ <div class='{res.style.mainBlock}'>
+ Well hello there
+ <span class='{res.style.nameSpan}' ui:field='userNameField'/>
+ </div>
+ </g:HTMLPanel>
+</ui:UiBinder>
+}}}
+
+</blockquote>
+
+=Share ImmutableResourceBundle instances=
+
+Extends LogoNamePanel (from the example above) to allow its bundle to be
passed in.
+
+{{{
+public class LogoNamePanel extends Composite {
+ interface MyUiBinder extend UiBinder<Widget, LogoNamePanel> {}
+ private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class);
+
+ @UiField SpanElement nameSpan;
+ final Resources resources;
+
+ public LogoNamePanel(Resources resources) {
+ initWidget(uiBinder.createAndBindUi(this));
+ this.resources = resources;
+ }
+
+ public void setUserName(String userName) {
+ nameSpan.setInnerText(userName);
+ }
+
+ @UiFactory
+ public Resources getResources() {
+ return resources;
+ }
+}
+}}}
+
+This can be even more concise:
+
+{{{
+public class LogoNamePanel extends Composite {
+ interface MyUiBinder extend UiBinder<Widget, LogoNamePanel> {}
+ 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.
+
+{{{
+<!-- UserDashboard.ui.xml -->
+< ui:UiBinder
+ xmlns:ui='urn:ui:com.google.gwt.uibinder'
+ xmlns:g='urn:import:com.google.gwt.user.client.ui'
+ xmlns:my='urn:import:com.my.app.widgets' >
+
+ <g:HTMLPanel>
+ <my:WeatherReport ui:field='weather'/>
+ <my:Stocks ui:field='stocks'/>
+ <my:CricketScores ui:field='scores' />
+ </g:HTMLPanel>
+</ui:UiBinder>
+}}}
+
+{{{
+public class UserDashboard extends Composite {
+ interface MyUiBinder extends UiBinder<Widget, UserDashboard> {}
+ private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class);
+
+ public UserDashboard() {
+ initWidget(uiBinder.createAndBindUi(this));
+ }
+}
+}}}
+
+An error results:
+
+{{{
+
+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<Widget, UserDashboard>;
+ 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<Widget, UserDashboard>;
+ 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(String... teamNames) {
+ return new CricketScores(teamNames);
+ }
+}
+}}}
+
+{{{
+<!-- UserDashboard.ui.xml -->
+<g:HTMLPanel xmlns:ui='urn:ui:com.google.gwt.uibinder'
+ xmlns:g='urn:import:com.google.gwt.user.client.ui'
+ xmlns:my='urn:import:com.my.app.widgets' >
+
+ <my:WeatherReport ui:field='weather'/>
+ <my:Stocks ui:field='stocks'/>
+ <my:CricketScores ui:field='scores' teamNames='AUS, SAF, WA, QLD, VIC'/>
+</g:HTMLPanel>
+}}}
+
+or annotate the constructor:
+
+{{{
+public @UiConstructor CricketScores(String... teamNames) {...}
+}}}
+
+{{{
+<!-- UserDashboard.ui.xml -->
+<g:HTMLPanel xmlns:ui='urn:ui:com.google.gwt.uibinder'
+ xmlns:g='urn:import:com.google.gwt.user.client.ui'
+ xmlns:my='urn:import:com.my.app.widgets' >
+
+ <my:WeatherReport ui:field='weather'/>
+ <my:Stocks ui:field='stocks'/>
+ <my:CricketScores ui:field='scores' teamNames='AUS, SAF, WA, QLD, VIC'/>
+</g:HTMLPanel>
+}}}
+
+or fill in a field marked with @UiField(provided=true):
+
+{{{
+public class UserDashboard extends Composite {
+ interface MyUiBinder extends UiBinder<Widget, UserDashboard>;
+ 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 MVC 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<Widget, FooPickerDisplay> {}
+ private static RedBinder redBinder = GWT.create(MyUiBinder.class);
+
+ @UiTemplate("BlueFooPicker.ui.xml")
+ interface BlueBinder extends UiBinder<Widget, FooPickerDisplay> {}
+ 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<Widget, FooPickerDisplay> binder) {
+ initWidget(uiBinder.createAndBindUi(this));
+ }
+
+ public static FooPickerDisplay createRedPicker() {
+ return new FooPickerDisplay(redBinder);
+ }
+
+ public static FooPickerDisplay createBluePicker() {
+ return new FooPickerDisplay(blueBinder);
+ }
+}
+}}}
--~--~---------~--~----~------------~-------~--~----~
http://groups.google.com/group/Google-Web-Toolkit-Contributors
-~----------~----~----~----~------~----~------~--~---