Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to combine multiple uiBinder-based widgets?

I need to insert a [number of] uiBinder-based widgets into another one, at a particular spot. The inserted widget has a somewhat complicated layout, so I am trying to define it in HTML.

referencePanel.add(...) fails with java.lang.IllegalStateException: This widget's parent does not implement HasWidgets. Don't know which widget's parent it's unhappy about - innerPanel or referencePanel.

If the ReferenceUI object is added to RootPanel, and then it's added to the bottom of the page. But if it's added to RootPanel first, then there is a JavaScriptException Code 3 (HIERARCHY_REQUEST_ERR) when added to referencePanel.

Any suggestions?

public class AppUIDemo extends Composite {
    @UiTemplate("AppUIDemo.ui.xml")
    interface AppUIDemoUiBinder extends UiBinder<Widget, AppUIDemo> {
    }

    @UiTemplate("ReferenceUI.ui.xml")
    interface ReferenceUIUiBinder extends
            UiBinder<Widget, ReferenceUI> {
    }

    private static AppUIDemoUiBinder uiBinder = GWT
            .create(AppUIDemoUiBinder.class);
    private static ReferenceUIUiBinder refUIBinder = GWT
            .create(ReferenceUIUiBinder.class);

    @UiField
    FlowPanel referencePanel;

    public AppUIDemo() {
            initWidget(uiBinder.createAndBindUi(this));
            ReferenceUI reference = new ReferenceUI(refUIBinder);

            HTMLPanel innerPanel = reference.getRefPanel();
            innerPanel.getElement().setId(HTMLPanel.createUniqueId());
            referencePanel.add(innerPanel);
        }
}

 

public class ReferenceUI extends Composite {

    interface ReferenceUIUiBinder extends
            UiBinder<Widget,ReferenceUI> {
    }

    private static ReferenceUIUiBinder uiBinder = GWT
            .create(ReferenceUIUiBinder.class);


    @UiField
HTMLPanel refPanel;

    public ReferenceUI() {
        initWidget(uiBinder.createAndBindUi(this));
    }

    public CreditReferenceUI(final UiBinder<Widget, CreditReferenceUI> binder) {
        initWidget(binder.createAndBindUi(this));
    }

    public HTMLPanel getRefPanel() {
        return refPanel;
    }
}

ReferenceUI.ui.xml

<!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"
    xmlns:gwittir="urn:import:com.totsp.gwittir.client.ui">
            <g:HTMLPanel ui:field="referencePanel">
            <table>
            <tr>
                <td>
                    <b>First Name</b></td>
                <td>
                    <b>Last Name</b></td>
                <td>
                    <b>Phone</b></td>
                <td>
                    <b>Fax</b></td>
            </tr>
            <tr>
                <td>
                    <gwittir:TextBox ui:field="referenceFirstName" styleName="input"/></td>
                <td>
                    <gwittir:TextBox ui:field="referenceLastName" styleName="input"/></td>
                <td>
                    <table><tr>
            <td> ( </td> <td>
                <gwittir:TextBox ui:field="referencePhoneAreaCode" styleName="input" maxLength="3"/></td>
                <td> ) </td> <td>
                <gwittir:TextBox ui:field="referencePhoneNumber" styleName="input" maxLength="7"/></td>
            <td> # </td> <td>
                <gwittir:TextBox ui:field="referencePhoneExtension" styleName="input" maxLength="25"/></td>
        </tr></table></td>
                <td>
                     <table><tr>
            <td> ( </td> <td>
                <gwittir:TextBox ui:field="referenceFaxAreaCode" styleName="input" maxLength="3"/></td>
                <td> ) </td> <td>
                <gwittir:TextBox ui:field="referenceFaxNumber" styleName="input" maxLength="7"/></td>
        </tr></table></td>
                </tr>
            <tr>
                <td style="text-align:right">
                    <b>Address: </b> Street</td>
                <td>
                    <gwittir:TextBox ui:field="referenceStreet" styleName="input"/></td>
                <td colspan="2" style="width:50%">
                    <table><tr><td>   City</td>
                    <td><gwittir:TextBox ui:field="referenceCity" styleName="input" maxLength="25"/></td>
                    <td> State </td>
                    <td class="state"><gwittir:TextBox ui:field="referenceState" styleName="input" maxLength="2"/></td>
                    <td> ZIP</td>
                    <td><gwittir:TextBox ui:field="referenceZIP" styleName="input" maxLength="9"/></td></tr></table>
                </td>
            </tr>
        </table>
    </g:HTMLPanel>
</ui:UiBinder>

AppUIDemo.ui.xml

<!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"
    xmlns:gwittir="urn:import:com.totsp.gwittir.client.ui">
    <g:HTMLPanel ui:field="basePanel">
        <!--  <div id="MainContent">  -->
        <p><h2><b>Application Demo</b></h2></p>

        <g:FlowPanel ui:field="referencePanel">
        </g:FlowPanel>
    </g:HTMLPanel>
</ui:UiBinder>
like image 611
jprusakova Avatar asked May 12 '10 19:05

jprusakova


2 Answers

I'll start with the fix, then move on to explain it afterward.

Fixing the problem

The easiest way to fix this problem is as follows. Here's your code:

HTMLPanel innerPanel = reference.getRefPanel();
innerPanel.getElement().setId(HTMLPanel.createUniqueId());
referencePanel.add(innerPanel);

Replace that with the following code. Note that only the last line has changed.

HTMLPanel innerPanel = reference.getRefPanel();
innerPanel.getElement().setId(HTMLPanel.createUniqueId());
referencePanel.add(reference);

This way, you are adding the Composite object. There will be no difference to the user, as the HTMLPanel (your innerPanel) will be directly attached into the document.


Some background

Adding a widget to a complex panel

When you add a widget to a complex panel (a panel that holds more than one child widget), four things happen one after the other:

  1. The widget is told to remove itself from its current parent
  2. The panel adds the widget to its list of children
  3. The panel adds the widget to the document
  4. The widget is told to set the panel as its new parent

Telling a widget to remove itself from its parent

When a child is told to remove itself from its parent, one of the following occurs:

  • If the widget does not have a parent, it does nothing
  • If the widget is the child of a panel that implements HasWidgets, the widget tells the panel to remove that widget
  • Otherwise, the widget throws IllegalStateException with message "This widget's parent does not implement HasWidgets

Composite widgets

When calling initWidget(Widget), the widget's parent is set to the Composite object.

The problem

When you try to add innerPanel, it is told to remove itself from its current parent. innerPanel is actually the root of your UiBinder template. Its parent is a Composite object (specifically, ReferenceUI). This results in the exception being thrown, as Composite does not implement HasWidgets and does not support removing its widget.

like image 123
Wesley Avatar answered Nov 15 '22 11:11

Wesley


Unfortunately, IIRC you can't set an id for a Widget in UiBinder's code (I think you can only for normal HTML). So you are left with setting the correct id via someWidget.getElement().setId() (like you did in the code above).

What's missing now, I think, is a placeholder (a div, span, whatever) with the correct id in the HTMLPanel referencePanel, like so:

<g:HTMLPanel ui:field="referencePanel">
    <div ui:field="referencePanelInner" />
</g:HTMLPanel>

And in AppUIDemo:

@UiField
DivElement referencePanelInner;
// ...
public AppUIDemo() {
    // ...
    referencePanelInner.setId(reference.getRefPanelId());
}

...unless you want to add the ReferenceUI directly into referencePanel - in which case you should just use a FlowPanel :)

PS: IMHO, you should generate and set the unique id for the ReferenceUI.refPanel from the ReferenceUI class (and expose the id) - that way you don't mess with the widget's "internals" from some external widgets.


OK, I think I got it. The IllegalStateException exception is being thrown because you are adding ReferenceUI.refPanel (or ReferenceUI.referencePanel, I don't know which is the current name) to AppUIDemo (ReferenceUI obviously doesn't implement HasWidgets) - when you should be adding the whole ReferenceUI reference composite :) I don't know why you did it this way in the first place (maybe because of the whole mess with the ids), but the code should look something like this:

public AppUIDemo() {
        initWidget(uiBinder.createAndBindUi(this));
        ReferenceUI reference = new ReferenceUI(refUIBinder);

        referencePanel.add(reference);
    }
like image 31
Igor Klimer Avatar answered Nov 15 '22 12:11

Igor Klimer