Revision: 6125
Author: rj...@google.com
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
-~----------~----~----~----~------~----~------~--~---

Reply via email to