How to combine multiple uiBinder-based widgets?
Asked Answered
C

2

6

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>
Celery answered 12/5, 2010 at 19:49 Comment(5)
Are you sure you define the id for the ReferenceUI composite? (or are you sure you have a holder with the the appropriate id in you ReferencePanel?) I don't see any in the code. BTW, could you provide the UiBinder code too?Gallaher
Reference panel in *.ui.xml is defined as <g:HTMLPanel ui:field="referencePanel">. How would I define the id for the composite - can you point to an example? There is no local UiBinder code, UiBinder is imported from com.google.gwt.uibinder.client.UiBinder.Celery
Can you post the UiBinder template for AppUIDemo as well as ReferenceUI? It would be nice to see exactly what the hierarchy of widgets looks like.Rociorock
...actually, for that matter, how are you able to call new ReferenceUI(refUIBinder);? Is that a typo of ClientReferenceUI?Rociorock
there is no ClientReferenceUI in the hierarchyCelery
R
13

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.

Rociorock answered 13/5, 2010 at 22:37 Comment(3)
+1 Nice, you got it right - I actually edited my answer before reading yours and it turns out that I basically wrote the same thing >_< (only in a less organized way ;))Gallaher
thank you very much, for a working answer, and detailed explanation.Celery
Glad it worked, jprusakova. And upvoted your answer Igor - it seems that great minds think alike!Rociorock
G
3

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);
    }
Gallaher answered 13/5, 2010 at 17:15 Comment(3)
I changed referencePanel to FlowPanel. The FlowPanel.add() does not take an id argument, just the widget to be added: referencePanel.add(reference); Different error now: java.lang.IllegalStateException: This widget's parent does not implement HasWidgets. Do you know which widget's parent it's unhappy about - innerPanel or referencePanel?Celery
It should point to the line at which the exception occurred - anything? And both HTMLPanel and FlowPanel of course implement HasWidgets, so this would mean that the problem is with your code - could you post the current *.ui.xml files?Gallaher
The exception points to generated JavaScript code, is where a way to trace it back to Java source?Celery

© 2022 - 2024 — McMap. All rights reserved.