Revision: 7305 Author: [email protected] Date: Sun Dec 13 13:50:48 2009 Log: Edited wiki page through web user interface. http://code.google.com/p/google-web-toolkit/source/detail?r=7305
Modified: /wiki/UiBinder.wiki ======================================= --- /wiki/UiBinder.wiki Tue Dec 8 05:58:37 2009 +++ /wiki/UiBinder.wiki Sun Dec 13 13:50:48 2009 @@ -2,551 +2,4 @@ = 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. - * <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: - -HelloWorld helloWorld = new HelloWorld("World"); -Document.get().getBody().appendChild(helloWorld.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"); -}}} - -= 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: - -{{{ -<!DOCTYPE ui:UiBinder SYSTEM "http://google-web-toolkit.googlecode.com/files/xhtml.ent"> -}}} - -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 <ui:style> element, you can define the CSS for your UI right -where you need it. - -{{{ -<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'> - <ui:style> - .pretty { background-color: Skyblue; } - </ui:style> - - <div class='{style.pretty}'> - Hello, <span ui:field='nameSpan'/>. - </div> -</ui:UiBinder> -}}} - -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: - -{{{ -<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'> - <ui:style> - .pretty { background-color: Skyblue; } - </ui:style> - <ui:style field='otherStyle'> - .pretty { background-color: Orange; } - </ui:style> - - <div class='{style.pretty}'> - Hello, <span class='{otherStyle.pretty}' ui:field='nameSpan'/>. - </div> -</ui:UiBinder> -}}} - -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: - -{{{ -<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'> - <ui:style src="MyUi.css" /> - <ui:style field='otherStyle' src="MyUiOtherStyle.css"> - - <div class='{style.pretty}'> - Hello, <span class='{otherStyle.pretty}' ui:field='nameSpan'/>. - </div> -</ui:UiBinder> -}}} - - -= Programatic access to inline Styles = - -(*type='' doesn't allow for multiple interfaces. Needs thinking*) - -{{{ - <ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'> - <ui:style type='com.my.app.MyFoo.MyStyle'> - .redBox { background-color:pink; border: 1px solid red; } - .enabled { color:black; } - .disabled { color:gray; } - </ui:style> - - <div class='{style.redBox} {style.enabled}'>I'm a box. And I'm red.</div> - </ui:UiBinder> -}}} - -{{{ - 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= - -{{{ -<!-- LogoNamePanel.ui.xml --> -<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder' - xmlns:g='urn:import:com.google.gwt.user.client.ui'> - - <ui:with field='res' type='com.my.app.widgets.logoname.Resources'/> - - <g:HTMLPanel> - - <g:Image resource='{res.logoImage}'> - - <div class='{res.style.mainBlock}'> - <div class='{res.style.userPictureSprite}' /> - <div> - Well hello there - <span class='{res.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 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<Widget, LogoNamePanel> {} - 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<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: *(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<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 makeCricketScores(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 [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<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(binder.createAndBindUi(this)); - } - - public static FooPickerDisplay createRedPicker() { - return new FooPickerDisplay(redBinder); - } - - public static FooPickerDisplay createBluePicker() { - return new FooPickerDisplay(blueBinder); - } -} -}}} +This page has been moved to http://code.google.com/webtoolkit/doc/latest/DevGuideUiBinder.html -- http://groups.google.com/group/Google-Web-Toolkit-Contributors
