Sub-classing UiBinder based Widgets

2,529 views
Skip to first unread message

Alexander Orlov

unread,
Jul 21, 2011, 10:32:43 AM7/21/11
to Google Web Toolkit
I have a widget "MyWidgetSuperClass extends Composite" that has an
UiBinder backed layout. And I have a "MyWidgetSubClass extends
MyWidgetSuperClass".

Now I can do

Button myButton; // this line alone doesn't brake anything
MyWidgetSubClass() {
widgetFieldOf_MyWidgetSuperClass.add(new Label("name")); // <<<
works
widgetFieldOf_MyWidgetSuperClass.add(myButton); // <<< fails here
}

to add new content to MyWidgetSubClass but adding a new field
(myButton) fails — why?

And is it also possible to extend MyWidgetSubClass.ui.xml adding new
elements to the existing XML layout and accessing them in
MyWidgetSubClass.java?

-Alex

Jeff Larsen

unread,
Jul 21, 2011, 10:51:04 AM7/21/11
to google-we...@googlegroups.com
Are you calling super()?

Button myButton doesn't look like it was initialized, was it? Also note, if Button myButton is being built by ui:binder it needs to have a @UiField and come after uiBinder.createAndBindUi.

Alexander Orlov

unread,
Jul 21, 2011, 11:14:17 AM7/21/11
to Google Web Toolkit
Now I have

@UiField // just omitted it to simplify the example
Button myButton; // only referenced in MyWidgetSubClass.ui.xml

interface Binder extends UiBinder<Widget, MyWidgetSubClass> {
}
final Binder binder = GWT.create(Binder.class);

MyWidgetSubClass() {
super(); // haven't called it before, wasn't sure what it does
binder.createAndBindUi(this); // using
initWidget(binder.createAndBindUi(this)) in MyWidgetSuperClass
widgetFieldOf_MyWidgetSuperClass.add(myButton);
}

Now the compilation works but MyWidgetSubClass looks just like
MyWidgetSuperClass.

-Alex

Thomas Broyer

unread,
Jul 21, 2011, 11:35:31 AM7/21/11
to google-we...@googlegroups.com
Generally speaking, you should prefer composition over inheritance. Separating your "superclass", into a "layout" widget (using @UiChild methods to allow adding child widgets when you use it in another ui.xml template) and a "presentation logic" code that you could possibly extend in a subclass, might help here.
(hey, look, we just reinvented MVP ;-) )

Thomas Broyer

unread,
Jul 21, 2011, 11:39:40 AM7/21/11
to google-we...@googlegroups.com
Forgot to say: I've used a similar approach where the "layout widget" is a subclass of my "superclass":

class MySuperClass {
  interface Binder extends UiBinder<Widget, Layout> { }
  static final Binder BINDER = GWT.create(Binder.class);

  static class Layout {
    private final MySuperClass owner;
    Layout(MySuperClass owner) { this.owner = owner; }

    // put you @UiField, @UiHandler, etc. here
  }

  final Layout layout;
  public MySuperClass() {
    this.layout = new Layout(this);
    BINDER.createAndBindUi(this.layout);
  }
  ...
}

Alexander Orlov

unread,
Jul 21, 2011, 11:39:19 AM7/21/11
to Google Web Toolkit
On Jul 21, 5:14 pm, Alexander Orlov <alexander.or...@loxal.net> wrote:
>         binder.createAndBindUi(this);

Ok, got it:

Widget app = binder.createAndBindUi(this);
RootPanel.get("widget").add(app);

...works, but the problem is that I want to bind everything to <div
id="widget"></div>. My "Main implements EntryPoint" layout looks like

<g:TabPanel ui:field="test">
<g:Tab>
<g:TabHTML>MyWidgetSuperClass</g:TabHTML>
<dp:MyWidgetSuperClass ui:field="myWidgetSuperClass"/>
</g:Tab>
<g:Tab>
<g:TabHTML>MyWidgetSubClass</g:TabHTML>
<dp:MyWidgetSubClass ui:field="myWidgetSubClass"/>
</g:Tab>
</g:TabPanel>

But binding the "Main implements EntryPoint" and MyWidgetSubClass to
the same DIV ID results in double output. So only MyWidgetSuperClass
is displayed within Main's widget boundaries.

-Alex

Alexander Orlov

unread,
Jul 21, 2011, 11:42:57 AM7/21/11
to Google Web Toolkit
Thx for this "best practice"(?) hint! I'll try to figure out how your
example you've posted later works.

-Alex

Alexander Orlov

unread,
Jul 21, 2011, 12:06:43 PM7/21/11
to Google Web Toolkit
Just messed up my code trying to implement this practice. Is there a
small actual example which is using two classes (super~ and sub~) with
their respective *.ui.xml's?

-Alex
Message has been deleted

gawelt

unread,
Jul 21, 2011, 9:55:22 PM7/21/11
to Google Web Toolkit
Hi,
I used to apply similar pattern as Thomas bt with some remarkable
difference.
here is the example to illustrate the differences and the concept
itself - it's quite simple and not show all possible combinations but
i think it's just enough for "getting started communication" - if you
were interested in it and need some hepl feel free to ask

import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.ButtonElement;
import com.google.gwt.dom.client.DivElement;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.FormElement;
import com.google.gwt.user.client.Element;
import com.google.gwt.dom.client.SpanElement;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.uibinder.client.UiTemplate;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.EventListener;
import com.google.gwt.user.client.ui.Widget;

public class ExampleUiBinderWidget extends Widget {

public static interface UI {
Element root();
Element someButton();
Element someLabel();
}

public static class DefaultUI implements UI {

//different from thomas - I used to inject UiBinder interface
definition
//inside "the layout inner class" - why look below


@UiTemplate("com.tomaszgawel.example.client.ExampleUiBinderWidget.ui.xml")
static interface DefaultUIBinder extends UiBinder<DivElement,
DefaultUI> {}
final static DefaultUIBinder uiBinder =
GWT.create(DefaultUIBinder.class);

//i could have declared this fields the same as return types from
//coresponding UI methods - so avoid necessity to cast later -
//but it is to show the flexibility of this approach
DivElement _root;
@UiField ButtonElement _someButton;
@UiField SpanElement _someLabel;

public DefaultUI(){
_root = uiBinder.createAndBindUi(this);
}
@Override public Element root() {
return _root.cast();
}
@Override public Element someButton() {
return _someButton.cast();
}
@Override public Element someLabel() {
return _someLabel.cast();
}
}

UI ui;
int counter;

public ExampleUiBinderWidget(){
this(new DefaultUI());
}

public ExampleUiBinderWidget(UI ui){
assert ui != null : "Provide ui - widgets do not like to show
naked :P";

//move initailisation from constructor
//in case you want call setElement on something else than ui.root()
this.ui = ui;
init();
}

protected void init() {
setElement(ui.root());
someInitActions();
}

protected void someInitActions() {
DOM.sinkEvents(ui.someButton(), Event.ONCLICK);
DOM.setEventListener(ui.someButton(), new EventListener() {
@Override public void onBrowserEvent(Event event) {
counter++;
ui.someLabel().setInnerText("Button has been clicked " + counter +
" times.");
}
});
}

}

class SomeSubClass extends ExampleUiBinderWidget {
public static interface UI extends ExampleUiBinderWidget.UI {
//wrappingFormElement
FormElement form();
}

//could just implenet the UI - but we also inherit superclass
DefaultUI
//to save some lines of duplicate code
static class DefaultUI extends ExampleUiBinderWidget.DefaultUI
implements UI {
@UiTemplate("com.tomaszgawel.example.client.SomeSubClass.ui.xml")
static interface DefaultUIBinder extends UiBinder<DivElement,
DefaultUI> {}
final static DefaultUIBinder uiBinder =
GWT.create(DefaultUIBinder.class);
@UiField FormElement _form;
public DefaultUI(){
_root = uiBinder.createAndBindUi(this);
}
@Override public FormElement form() {
return _form;
}
}

public SomeSubClass(){
super(new DefaultUI());
}

public SomeSubClass(UI ui){
super(ui);
}

@Override
protected void init() {
//you must cast to subclasses UI to get to additional fields
//as member field ui is of type ExampleUiBinderWidget.UI
((UI) ui).form().setAction("http://someserver.com");

//and you could do this before call to super.init
//as it is not a constructor

super.init();

//if you wuld prefer to set ELement on form not root
//instead of calling super.init just insert lines like this:
//setElement(((UI) ui).form());
//someInitActions();

}

}

class Test {

public static void test(){
//as we have constructor with UI as argument
//you can provide any UI defined outside the class
//only if it implements UI interface
//it can even not have anything to do with uibinder
//consider the UI that looks for ELement in html page
//(yes - that was why I extended Widget not Composite in that
example)

SomeSubClass ssc = new SomeSubClass(new SomeSubClass.UI() {
FormElement _form;
Element _root;
Element _button;
Element _label;

//initialisation block as anonymous class does not
//have a constructor
{
_root = Document.get().getBody().cast();
_form = _root.getElementsByTagName("form").getItem(0).cast();
_label = DOM.getElementById("someLabelOnThePage");
_button = _form.getElementsByTagName("button").getItem(0).cast();
assert _form != null && _root != null && _button != null &&
_label != null;
}

@Override public Element someLabel() {
return _label;
}
@Override public Element someButton() {
return _button;
}
@Override public Element root() {
return _root;
}
@Override public FormElement form() {
return _form;
}
});
}

}

Alexander Orlov

unread,
Jul 22, 2011, 5:09:27 AM7/22/11
to Google Web Toolkit
What is the advantage of this practice to Thomas' proposal?

DefaultUI.ui.xml is missing in your example.I've implemented the
DefaultUI.ui.xml manually:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
<ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder"
xmlns:g="urn:import:com.google.gwt.user.client.ui"
>
<g:VerticalPanel>
<g:SpanElement ui:field="_someLabel"/>
<g:ButtonElement ui:field="_someButton"/>
</g:VerticalPanel>
</ui:UiBinder>

SomeSubClass.ui.xml is missing in your example.I've implemented the
DefaultUI.ui.xml manually:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
<ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder"
xmlns:g="urn:import:com.google.gwt.user.client.ui"
>
<g:VerticalPanel>
<g:FormElement ui:field="_form"/>
</g:VerticalPanel>
</ui:UiBinder>


...didn't solve the problem: The example fails to compile =>

DEBUG: Rebinding
dp.verp.planer.client.ExampleUiBinderWidget.DefaultUI.DefaultUIBinder.
ERROR: Deferred binding failed for
'dp.verp.planer.client.ExampleUiBinderWidget.DefaultUI.DefaultUIBinder';
expect subsequent failures.

I've changed
@UiTemplate("com.tomaszgawel.example.client.ExampleUiBinderWidget.ui.xml")
to
@UiTemplate("dp.verp.planer.client.ExampleUiBinderWidget.ui.xml")

and
@UiTemplate("com.tomaszgawel.example.client.SomeSubClass.ui.xml")
to
@UiTemplate("dp.verp.planer.client.SomeSubClass.ui.xml")

-Alex

gawelt

unread,
Jul 22, 2011, 6:27:19 AM7/22/11
to Google Web Toolkit

Alex,

DefaultUI.ui.xml and SomeSubClass.ui.xml are not needed for anything
in that example. You should provide
dp.verp.planer.client.ExampleUiBinderWidget.ui.xml and
dp.verp.planer.client.SomeSubClass.ui.xml instead, where
"dp.verp.planer.client." is the JNDI path to your templates (e.g. they
should be in dp/verp/planer/client/ directory on your classpath - I
pointed custom path to template with @UiTemplate annotation not to
rely on default as it could tedious with UiBinder template bound to
inner static class).

gawelt

unread,
Jul 22, 2011, 6:29:37 AM7/22/11
to Google Web Toolkit
... and the advantage over the thomas' pattern is that you can incject
any UI implementation form outside withouth defining subclass of the
widget. and it can be implemented WITH or WITHOUT uibinder or even
attached to existing DOM Nodes. - read comments in the example above.

Alexander Orlov

unread,
Jul 22, 2011, 7:03:55 AM7/22/11
to Google Web Toolkit
Ok, got your example going...

On Jul 21, 5:35 pm, Thomas Broyer <t.bro...@gmail.com> wrote:
> (using @UiChild methods
> to allow adding child widgets when you use it in another ui.xml template)

That's basically what I want. Is it also possible to declare some
methods abstract to change their logic for child widgets or is exactly
this what you mean with using @UiChild annotations for methods?

class SuperClass {
// ..
static class Layout {
// ...
// put you @UiField, @UiHandler, etc. here
@UiField
Button test;
}

final Layout layout;

public SuperClass() {
this.layout = new Layout(this);

RootPanel.get("widget").add(BINDER.createAndBindUi(this.layout));
}

@UiChild
public void myMethod(String text) {
// ...is this the proper use for @UiChild methods?
}

public void onModuleLoad() {
}
}

And where can I assign a handler to the "test" button?

-Alex

Jens

unread,
Jul 22, 2011, 7:22:31 AM7/22/11
to google-we...@googlegroups.com
If you have a custom widget, e.g. "MyPanel", you can do something like the following in any other *.ui.xml file:

<my:MyPanel>
  <my:header>
     <g:Label>Header</g:Label>
  </my:header>

  <my:footer>
    <g:Label>Footer</g:Label>
  </my:footer>
</my:MyPanel>

Your Java implementation of MyPanel would then contain methods:

@UiChild
public void addHeader(Widget w) {
 //your add logic
}

@UiChild
public void addFooter(Widget w) {
  //your add logic
}

Its up to you how you add the widgets to MyPanel and of course you could simply do an instanceof check if you want to add things differently based on the widget that should be added.

-- J.

Alexander Orlov

unread,
Jul 22, 2011, 7:50:06 AM7/22/11
to google-we...@googlegroups.com
I guess therefore you're using WidgetElements instead of Widgets (ButtonElement instead of Button)? This technique wouldn't work without using WidgetElements?

-Alex

Thomas Broyer

unread,
Jul 22, 2011, 8:23:50 AM7/22/11
to google-we...@googlegroups.com


On Friday, July 22, 2011 1:03:55 PM UTC+2, Alexander Orlov wrote:
class SuperClass {
    // ..
    static class Layout {
        // ...
        // put you @UiField, @UiHandler, etc. here
        @UiField
        Button test;
    }

    final Layout layout;

    public SuperClass() {
        this.layout = new Layout(this);
 
RootPanel.get("widget").add(BINDER.createAndBindUi(this.layout));

D'oh, this is not at all the place to do this kind of thing. You should stick with the "create something, then put it somewhere" pattern (rather than a "create something, and it'll automatically show at a predefined location")
 
And where can I assign a handler to the "test" button?

In the Layout inner class, and you can simply defer to your outer class:
abstract class SuperClass { 
    ...
    static class Layout { 
        private final SuperClass owner;
        ...
        // put you @UiField, @UiHandler, etc. here 
        @UiField 
        Button test; 

        @UiHandler("test")
        void onTestClicked(ClickEvent event) {
          this.owner.onTestClicked();
    }

    protected abstract void onTestClicked();
    ...
}

BTW, I'm not saying this is the best way of doing it, but it works, and it shows what should be obvious: you're not forced to pass 'this' to createAndBindUi (and incidentally to call createAndBindUi from an "UiBinder owner class" instance).
I still believe it's better to make a reusable widget, using @UiChild to allow adding children to it and events/delegate to communicate changes to the container (or actually anything using it).

Tomasz Gawel

unread,
Jul 22, 2011, 9:42:22 AM7/22/11
to Google Web Toolkit
@Alex,
When you extend Composite not Widget/UiObject, you "init it" with
initWidget() not setElement - you cannot provide UI implementations
that takes use of existing DOM Nodes in html page. without this
exception thera no other differences.

Alexander Orlov

unread,
Jul 22, 2011, 10:18:37 AM7/22/11
to google-we...@googlegroups.com
On Fri, Jul 22, 2011 at 1:22 PM, Jens <jens.ne...@gmail.com> wrote:
<my:MyPanel>
  <my:header>
     <g:Label>Header</g:Label>
  </my:header>
</my:MyPanel>

Your Java implementation of MyPanel would then contain methods:

@UiChild
public void addHeader(Widget w) {
 //your add logic
}
 
Great, that's basically everything I need!

-Alex

othman

unread,
Oct 25, 2012, 8:51:56 PM10/25/12
to google-we...@googlegroups.com
gwt-platform have a better design for nested Presenters.
That's where nesting presenters becomes handy, because you would create a upper layer presenter that has a content slot from every other presenters to be shown in and only this one would have the header and the footer in it. I always call this presenter : ApplicationPresenter.

You can find an example in here:
http://code.google.com/p/gwt-platform/wiki/GettingStarted?tm=6#Nested_presenters
Reply all
Reply to author
Forward
0 new messages