views:

678

answers:

2

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"&gt;
<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"&gt;
<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>
+2  A: 

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);
    }
Igor Klimer
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?
jprusakova
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?
Igor Klimer
The exception points to generated JavaScript code, is where a way to trace it back to Java source?
jprusakova
Added ReferenceUI.ui.xml code to the question above.
jprusakova
thanks for your help!
jprusakova
+3  A: 

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.

Wesley
+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 ;))
Igor Klimer
thank you very much, for a working answer, and detailed explanation.
jprusakova
Glad it worked, jprusakova. And upvoted your answer Igor - it seems that great minds think alike!
Wesley