Author: [email protected]
Date: Fri Mar 6 07:55:47 2009
New Revision: 4948
Added:
releases/1.6/user/test/com/google/gwt/user/client/ui/RootPanelTest.java
Modified:
releases/1.6/user/src/com/google/gwt/i18n/client/BidiUtils.java
releases/1.6/user/src/com/google/gwt/user/client/DOM.java
releases/1.6/user/src/com/google/gwt/user/client/Event.java
releases/1.6/user/src/com/google/gwt/user/client/impl/DOMImpl.java
releases/1.6/user/src/com/google/gwt/user/client/ui/HTMLPanel.java
releases/1.6/user/src/com/google/gwt/user/client/ui/RootPanel.java
releases/1.6/user/src/com/google/gwt/user/client/ui/SuggestBox.java
releases/1.6/user/src/com/google/gwt/user/client/ui/Widget.java
releases/1.6/user/test/com/google/gwt/user/UISuite.java
releases/1.6/user/test/com/google/gwt/user/client/ui/ElementWrappingTest.java
releases/1.6/user/test/com/google/gwt/user/client/ui/HTMLPanelTest.java
releases/1.6/user/test/com/google/gwt/user/client/ui/SuggestBoxTest.java
Log:
This patch fixes the following issues:
- issue 1937: RootPanel no longer inappropriately caches the results of
getElementById().
- issue 3040: SuggestBox gets a wrap() method.
- HTMLPanel now cleans up its hiddenDiv immediately after using it (c.f. the
long discussion attached to issue 1937).
- General wrapping cleanup and simplification (mostly needed for
SuggestBox.wrap(), but also generally useful):
- It is now possible to wrap() an Element in the DOM and move the wrapped
widget to another parent, including a composite.
- RootPanel no longer throws an assertion error if it discovers a
widget in
the detach list on unload. Rather, it now asserts in
detachOnWindowClose() to see whether the element being wrapped has a
parent widget anywhere in its hierarchy. This throws an exception
closer
to the site of the error.
Patch by: jgw
Review by: jlabanca
Modified: releases/1.6/user/src/com/google/gwt/i18n/client/BidiUtils.java
==============================================================================
--- releases/1.6/user/src/com/google/gwt/i18n/client/BidiUtils.java
(original)
+++ releases/1.6/user/src/com/google/gwt/i18n/client/BidiUtils.java Fri
Mar 6 07:55:47 2009
@@ -15,8 +15,7 @@
*/
package com.google.gwt.i18n.client;
-import com.google.gwt.user.client.Element;
-import com.google.gwt.user.client.DOM;
+import com.google.gwt.dom.client.Element;
import com.google.gwt.i18n.client.HasDirection.Direction;
/**
@@ -50,8 +49,7 @@
* <code>DEFAULT</code> if the directionality is not explicitly
set
*/
public static HasDirection.Direction getDirectionOnElement(Element elem)
{
-
- String dirPropertyValue = DOM.getElementProperty(elem,
DIR_PROPERTY_NAME);
+ String dirPropertyValue = elem.getPropertyString(DIR_PROPERTY_NAME);
if (DIR_PROPERTY_VALUE_RTL.equalsIgnoreCase(dirPropertyValue)) {
return HasDirection.Direction.RTL;
@@ -71,15 +69,14 @@
* <code>DEFAULT</code> if the directionality should be
removed from the element
*/
public static void setDirectionOnElement(Element elem, Direction
direction) {
-
switch (direction) {
case RTL: {
- DOM.setElementProperty(elem, DIR_PROPERTY_NAME,
DIR_PROPERTY_VALUE_RTL);
+ elem.setPropertyString(DIR_PROPERTY_NAME, DIR_PROPERTY_VALUE_RTL);
break;
}
case LTR: {
- DOM.setElementProperty(elem, DIR_PROPERTY_NAME,
DIR_PROPERTY_VALUE_LTR);
+ elem.setPropertyString(DIR_PROPERTY_NAME, DIR_PROPERTY_VALUE_LTR);
break;
}
@@ -87,7 +84,7 @@
if (getDirectionOnElement(elem) != HasDirection.Direction.DEFAULT)
{
// only clear out the the dir property if it has already been
set to something
// explicitly
- DOM.setElementProperty(elem, DIR_PROPERTY_NAME, "");
+ elem.setPropertyString(DIR_PROPERTY_NAME, "");
}
break;
}
Modified: releases/1.6/user/src/com/google/gwt/user/client/DOM.java
==============================================================================
--- releases/1.6/user/src/com/google/gwt/user/client/DOM.java (original)
+++ releases/1.6/user/src/com/google/gwt/user/client/DOM.java Fri Mar 6
07:55:47 2009
@@ -784,6 +784,17 @@
}
/**
+ * Gets the {...@link EventListener} that will receive events for the given
+ * element. Only one such listener may exist for a single element.
+ *
+ * @param elem the element whose listener is to be set
+ * @return the element's event listener
+ */
+ public static EventListener getEventListener(Element elem) {
+ return impl.getEventListener(elem);
+ }
+
+ /**
* Gets the current set of events sunk by a given element.
*
* @param elem the element whose events are to be retrieved
Modified: releases/1.6/user/src/com/google/gwt/user/client/Event.java
==============================================================================
--- releases/1.6/user/src/com/google/gwt/user/client/Event.java (original)
+++ releases/1.6/user/src/com/google/gwt/user/client/Event.java Fri Mar 6
07:55:47 2009
@@ -503,6 +503,17 @@
}
/**
+ * Gets the {...@link EventListener} that will receive events for the given
+ * element. Only one such listener may exist for a single element.
+ *
+ * @param elem the element whose listener is to be set
+ * @return the element's event listener
+ */
+ public static EventListener getEventListener(Element elem) {
+ return DOM.getEventListener((com.google.gwt.user.client.Element) elem);
+ }
+
+ /**
* Sets the {...@link EventListener} to receive events for the given
element.
* Only one such listener may exist for a single element.
*
Modified: releases/1.6/user/src/com/google/gwt/user/client/impl/DOMImpl.java
==============================================================================
--- releases/1.6/user/src/com/google/gwt/user/client/impl/DOMImpl.java
(original)
+++ releases/1.6/user/src/com/google/gwt/user/client/impl/DOMImpl.java Fri
Mar 6 07:55:47 2009
@@ -100,6 +100,10 @@
public abstract int getChildIndex(Element parent, Element child);
+ public native EventListener getEventListener(Element elem) /*-{
+ return elem.__listener;
+ }-*/;
+
public native int getEventsSunk(Element elem) /*-{
return elem.__eventBits || 0;
}-*/;
Modified: releases/1.6/user/src/com/google/gwt/user/client/ui/HTMLPanel.java
==============================================================================
--- releases/1.6/user/src/com/google/gwt/user/client/ui/HTMLPanel.java
(original)
+++ releases/1.6/user/src/com/google/gwt/user/client/ui/HTMLPanel.java Fri
Mar 6 07:55:47 2009
@@ -116,7 +116,7 @@
if (hiddenDiv == null) {
hiddenDiv = DOM.createDiv();
UIObject.setVisible(hiddenDiv, false);
- DOM.appendChild(RootPanel.getBodyElement(), hiddenDiv);
+ RootPanel.getBodyElement().appendChild(hiddenDiv);
}
// Hang on to the panel's original parent and sibling elements so that
it
@@ -133,6 +133,8 @@
// Put the panel's element back where it was.
if (origParent != null) {
DOM.insertBefore(origParent, getElement(), origSibling);
+ } else {
+ DOM.removeChild(hiddenDiv, getElement());
}
return child;
Modified: releases/1.6/user/src/com/google/gwt/user/client/ui/RootPanel.java
==============================================================================
--- releases/1.6/user/src/com/google/gwt/user/client/ui/RootPanel.java
(original)
+++ releases/1.6/user/src/com/google/gwt/user/client/ui/RootPanel.java Fri
Mar 6 07:55:47 2009
@@ -15,14 +15,16 @@
*/
package com.google.gwt.user.client.ui;
+import com.google.gwt.dom.client.BodyElement;
import com.google.gwt.dom.client.Document;
import com.google.gwt.event.logical.shared.CloseEvent;
import com.google.gwt.event.logical.shared.CloseHandler;
import com.google.gwt.i18n.client.BidiUtils;
import com.google.gwt.i18n.client.HasDirection;
import com.google.gwt.i18n.client.LocaleInfo;
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Element;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.EventListener;
import com.google.gwt.user.client.Window;
import java.util.HashMap;
@@ -32,7 +34,8 @@
/**
* The panel to which all other widgets must ultimately be added.
RootPanels are
- * never created directly. Rather, they are accessed via {...@link
RootPanel#get()}.
+ * never created directly. Rather, they are accessed via {...@link
RootPanel#get()}
+ * .
*
* <p>
* Most applications will add widgets to the default root panel in their
@@ -67,24 +70,24 @@
/**
* Marks a widget as detached and removes it from the detach list.
*
+ * <p>
* If an element belonging to a widget originally passed to
- * {...@link #detachOnWindowClose(Widget)} has been removed from the
document, calling
- * this method will cause it to be marked as detached immediately.
Failure to
- * do so will keep the widget from being garbage collected until the
page is
- * unloaded.
+ * {...@link #detachOnWindowClose(Widget)} has been removed from the
document,
+ * calling this method will cause it to be marked as detached
immediately.
+ * Failure to do so will keep the widget from being garbage collected
until
+ * the page is unloaded.
+ * </p>
*
+ * <p>
* This method may only be called per widget, and only for widgets that
were
- * originally passed to {...@link #detachOnWindowClose(Widget)}. Any widget
in the
- * detach list, whose element is no longer in the document when the page
- * unloads, will cause an assertion error.
+ * originally passed to {...@link #detachOnWindowClose(Widget)}.
+ * </p>
*
* @param widget the widget that no longer needs to be cleaned up when
the
* page closes
* @see #detachOnWindowClose(Widget)
*/
public static void detachNow(Widget widget) {
-
assert !getBodyElement().isOrHasChild(widget.getElement()) : "detachNow() "
- + "called on a widget whose element is still attached to the
document";
assert widgetsToDetach.contains(widget) : "detachNow() called on a
widget "
+ "not currently in the detach list";
@@ -96,12 +99,20 @@
* Adds a widget to the detach list. This is the list of widgets to be
* detached when the page unloads.
*
+ * <p>
* This method must be called for all widgets that have no parent
widgets.
* These are most commonly {...@link RootPanel RootPanels}, but can also be
any
* widget used to wrap an existing element on the page. Failing to do
this may
* cause these widgets to leak memory. This method is called
automatically by
* widgets' wrap methods (e.g.
* {...@link Button#wrap(com.google.gwt.dom.client.Element)}).
+ * </p>
+ *
+ * <p>
+ * This method may <em>not</em> be called on any widget whose element is
+ * contained in another widget. This is to ensure that the DOM and Widget
+ * hierarchies cannot get into an inconsistent state.
+ * </p>
*
* @param widget the widget to be cleaned up when the page closes
* @see #detachNow(Widget)
@@ -109,12 +120,14 @@
public static void detachOnWindowClose(Widget widget) {
assert !widgetsToDetach.contains(widget) : "detachOnUnload() called
twice "
+ "for the same widget";
+ assert !isElementChildOfWidget(widget.getElement()) : "A widget that
has "
+ + "an existing parent widget may not be added to the detach list";
widgetsToDetach.add(widget);
}
/**
- * Gets the default root panel. This panel wraps body of the browser's
+ * Gets the default root panel. This panel wraps the body of the
browser's
* document. This root panel can contain any number of widgets, which
will be
* laid out in their natural HTML ordering. Many applications, however,
will
* add a single panel to the RootPanel to provide more structure.
@@ -130,24 +143,34 @@
* work, the HTML document into which the application is loaded must have
* specified an element with the given id.
*
- * @param id the id of the element to be wrapped with a root panel
+ * @param id the id of the element to be wrapped with a root panel (
+ * <code>null</code> specifies the default instance, which
wraps the
+ * <body> element)
* @return the root panel, or <code>null</code> if no such element was
found
*/
public static RootPanel get(String id) {
// See if this RootPanel is already created.
RootPanel rp = rootPanels.get(id);
- if (rp != null) {
- return rp;
- }
// Find the element that this RootPanel will wrap.
Element elem = null;
if (id != null) {
- if (null == (elem = DOM.getElementById(id))) {
+ // Return null if the id is specified, but no element is found.
+ if (null == (elem = Document.get().getElementById(id))) {
return null;
}
}
+ if (rp != null) {
+ // If the element associated with an existing RootPanel has been
replaced
+ // for any reason, return a new RootPanel rather than the existing
one (
+ // see issue 1937).
+ if ((elem == null) || (rp.getElement() == elem)) {
+ // There's already an existing RootPanel for this element. Return
it.
+ return rp;
+ }
+ }
+
// Note that the code in this if block only happens once -
// on the first RootPanel.get(String) or RootPanel.get()
// call.
@@ -181,10 +204,20 @@
*
* @return the document's body element
*/
- public static native Element getBodyElement() /*-{
+ public static native com.google.gwt.user.client.Element getBodyElement()
/*-{
return $doc.body;
}-*/;
+ /**
+ * Determines whether the given widget is in the detach list.
+ *
+ * @param widget the widget to be checked
+ * @return <code>true</code> if the widget is in the detach list
+ */
+ public static boolean isInDetachList(Widget widget) {
+ return widgetsToDetach.contains(widget);
+ }
+
// Package-protected for use by unit tests. Do not call this method
directly.
static void detachWidgets() {
// When the window is closing, detach all widgets that need to be
@@ -194,16 +227,9 @@
if (widget.isAttached()) {
widget.onDetach();
}
-
- // Assert that each widget's element is actually attached to the
- // document. If not, then it was probably wrapped and removed, but
not
- // properly detached.
- assert getBodyElement().isOrHasChild(widget.getElement()) : "A "
- + "widget in the detach list was found not attached to the "
- + "document. The is likely caused by wrapping an existing "
- + "element and removing it from the document without calling "
- + "RootPanel.detachNow().";
}
+
+ widgetsToDetach.clear();
}
/**
@@ -224,8 +250,29 @@
});
}
+ /*
+ * Checks to see whether the given element has any parent element that
+ * belongs to a widget. This is not terribly efficient, and is thus only
used
+ * in an assertion.
+ */
+ private static boolean isElementChildOfWidget(Element element) {
+ // Walk up the DOM hierarchy, looking for any widget with an event
listener
+ // set. Though it is not dependable in the general case that a widget
will
+ // have set its element's event listener at all times, it *is*
dependable
+ // if the widget is attached. Which it will be in this case.
+ element = element.getParentElement();
+ BodyElement body = Document.get().getBody();
+ while ((element != null) && (body != element)) {
+ if (Event.getEventListener(element) != null) {
+ return true;
+ }
+ element = element.getParentElement().cast();
+ }
+ return false;
+ }
+
private RootPanel(Element elem) {
- super(elem);
+ super(elem.<com.google.gwt.user.client.Element> cast());
onAttach();
}
}
Modified:
releases/1.6/user/src/com/google/gwt/user/client/ui/SuggestBox.java
==============================================================================
--- releases/1.6/user/src/com/google/gwt/user/client/ui/SuggestBox.java
(original)
+++ releases/1.6/user/src/com/google/gwt/user/client/ui/SuggestBox.java Fri
Mar 6 07:55:47 2009
@@ -15,6 +15,8 @@
*/
package com.google.gwt.user.client.ui;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.Element;
import com.google.gwt.event.dom.client.HandlesAllKeyEvents;
import com.google.gwt.event.dom.client.HasAllKeyHandlers;
import com.google.gwt.event.dom.client.KeyCodes;
@@ -207,6 +209,7 @@
}
}
+
/**
* Class for menu items in a SuggestionMenu. A SuggestionMenuItem
differs from
* a MenuItem in that each item is backed by a Suggestion object. The
text of
@@ -240,6 +243,31 @@
}
private static final String STYLENAME_DEFAULT = "gwt-SuggestBox";
+
+ /**
+ * Creates a {...@link SuggestBox} widget that wraps an existing <input
+ * type='text'> element.
+ *
+ * This element must already be attached to the document. If the element
is
+ * removed from the document, you must call
+ * {...@link RootPanel#detachNow(Widget)}.
+ *
+ * @param oracle the suggest box oracle to use
+ * @param element the element to be wrapped
+ */
+ public static SuggestBox wrap(SuggestOracle oracle, Element element) {
+ // Assert that the element is attached.
+ assert Document.get().getBody().isOrHasChild(element);
+
+ TextBox textBox = new TextBox(element);
+ SuggestBox suggestBox = new SuggestBox(oracle, textBox);
+
+ // Mark it attached and remember it for cleanup.
+ suggestBox.onAttach();
+ RootPanel.detachOnWindowClose(suggestBox);
+
+ return suggestBox;
+ }
private int limit = 20;
private boolean selectsFirstItem = true;
Modified: releases/1.6/user/src/com/google/gwt/user/client/ui/Widget.java
==============================================================================
--- releases/1.6/user/src/com/google/gwt/user/client/ui/Widget.java
(original)
+++ releases/1.6/user/src/com/google/gwt/user/client/ui/Widget.java Fri
Mar 6 07:55:47 2009
@@ -89,16 +89,29 @@
}
DomEvent.fireNativeEvent(event, this);
}
-
+
/**
- * Removes this widget from its parent widget. If it has no parent, this
- * method does nothing.
+ * Removes this widget from its parent widget, if one exists.
+ *
+ * <p>
+ * If it has no parent, this method does nothing. If it is a "root"
widget
+ * (meaning it's been added to the detach list via
+ * {...@link RootPanel#detachOnWindowClose(Widget)}), it will be removed
from the
+ * detached immediately. This makes it possible for Composites and
Panels to
+ * adopt root widgets.
+ * </p>
*
* @throws IllegalStateException if this widget's parent does not support
* removal (e.g. {...@link Composite})
*/
public void removeFromParent() {
- if (parent instanceof HasWidgets) {
+ if (parent == null) {
+ // If the widget had no parent, check to see if it was in the detach
list
+ // and remove it if necessary.
+ if (RootPanel.isInDetachList(this)) {
+ RootPanel.detachNow(this);
+ }
+ } else if (parent instanceof HasWidgets) {
((HasWidgets) parent).remove(this);
} else if (parent != null) {
throw new IllegalStateException(
Modified: releases/1.6/user/test/com/google/gwt/user/UISuite.java
==============================================================================
--- releases/1.6/user/test/com/google/gwt/user/UISuite.java (original)
+++ releases/1.6/user/test/com/google/gwt/user/UISuite.java Fri Mar 6
07:55:47 2009
@@ -15,6 +15,8 @@
*/
package com.google.gwt.user;
+import junit.framework.Test;
+
import com.google.gwt.junit.tools.GWTTestSuite;
import com.google.gwt.user.client.CommandExecutorTest;
import com.google.gwt.user.client.CookieTest;
@@ -22,7 +24,6 @@
import com.google.gwt.user.client.WindowTest;
import com.google.gwt.user.client.ui.AbsolutePanelTest;
import com.google.gwt.user.client.ui.AnchorTest;
-import com.google.gwt.user.client.ui.WidgetTest;
import com.google.gwt.user.client.ui.ButtonTest;
import com.google.gwt.user.client.ui.CaptionPanelTest;
import com.google.gwt.user.client.ui.CheckBoxTest;
@@ -65,6 +66,7 @@
import com.google.gwt.user.client.ui.PrefixTreeTest;
import com.google.gwt.user.client.ui.RadioButtonTest;
import com.google.gwt.user.client.ui.RichTextAreaTest;
+import com.google.gwt.user.client.ui.RootPanelTest;
import com.google.gwt.user.client.ui.ScrollPanelTest;
import com.google.gwt.user.client.ui.SimpleCheckBoxTest;
import com.google.gwt.user.client.ui.SimpleRadioButtonTest;
@@ -81,13 +83,12 @@
import com.google.gwt.user.client.ui.WidgetIteratorsTest;
import com.google.gwt.user.client.ui.WidgetOnLoadTest;
import com.google.gwt.user.client.ui.WidgetSubclassingTest;
+import com.google.gwt.user.client.ui.WidgetTest;
import com.google.gwt.user.client.ui.impl.ClippedImagePrototypeTest;
import com.google.gwt.user.datepicker.client.DateChangeEventTest;
import com.google.gwt.user.rebind.ui.ImageBundleGeneratorTest;
import com.google.gwt.xml.client.XMLTest;
-import junit.framework.Test;
-
/**
* Tests of the ui package.
*/
@@ -166,6 +167,7 @@
suite.addTestSuite(DateChangeEventTest.class);
suite.addTestSuite(CreateEventTest.class);
suite.addTestSuite(WidgetTest.class);
+ suite.addTestSuite(RootPanelTest.class);
return suite;
}
}
Modified:
releases/1.6/user/test/com/google/gwt/user/client/ui/ElementWrappingTest.java
==============================================================================
---
releases/1.6/user/test/com/google/gwt/user/client/ui/ElementWrappingTest.java
(original)
+++
releases/1.6/user/test/com/google/gwt/user/client/ui/ElementWrappingTest.java
Fri Mar 6 07:55:47 2009
@@ -34,6 +34,9 @@
return "com.google.gwt.user.UserTest";
}
+ /**
+ * Tests {...@link Anchor#wrap(Element)}.
+ */
public void testAnchor() {
ensureDiv().setInnerHTML("<a id='foo' href='" + TEST_URL
+ "'>myAnchor</a>");
Anchor anchor = Anchor.wrap(Document.get().getElementById("foo"));
@@ -43,6 +46,9 @@
assertEquals("myAnchor", anchor.getText());
}
+ /**
+ * Tests {...@link Button#wrap(Element)}.
+ */
public void testButton() {
ensureDiv().setInnerHTML("<button id='foo'>myButton</button>");
Button button = Button.wrap(Document.get().getElementById("foo"));
@@ -51,6 +57,51 @@
assertEquals("myButton", button.getText());
}
+ /**
+ * Tests that {...@link RootPanel#detachNow(Widget)} can only be called
once per
+ * widget.
+ */
+ public void testDetachNowTwiceFails() {
+ // Testing hosted-mode-only assertion.
+ if (!GWT.isScript()) {
+ try {
+ // Trying to pass the same widget to RootPanel.detachNow() twice
+ // should fail an assertion.
+ ensureDiv().setInnerHTML(
+ "<a id='foo' href='" + TEST_URL + "'>myAnchor</a>");
+ Anchor a = Anchor.wrap(Document.get().getElementById("foo"));
+ RootPanel.detachNow(a); // pass
+ RootPanel.detachNow(a); // fail
+ fail("Expected assertion failure calling detachNow() twice");
+ } catch (AssertionError e) {
+ }
+ }
+ }
+
+ /**
+ * Tests that {...@link RootPanel#detachOnWindowClose(Widget)} can only be
called
+ * once per widget.
+ */
+ public void testDetachOnWindowCloseTwiceFails() {
+ // Testing hosted-mode-only assertion.
+ if (!GWT.isScript()) {
+ try {
+ // Trying to pass the same widget to RootPanel.detachOnUnload()
twice
+ // should fail an assertion (the first call is implicit through
+ // Anchor.wrap()).
+ ensureDiv().setInnerHTML(
+ "<a id='foo' href='" + TEST_URL + "'>myAnchor</a>");
+ Anchor a = Anchor.wrap(Document.get().getElementById("foo")); //
pass
+ RootPanel.detachOnWindowClose(a); // fail
+ fail("Expected assertion failure calling detachOnLoad() twice");
+ } catch (AssertionError e) {
+ }
+ }
+ }
+
+ /**
+ * Tests {...@link FileUpload#wrap(Element)}.
+ */
public void testFileUpload() {
ensureDiv().setInnerHTML("<input type='file'
id='foo'>myInput</input>");
FileUpload upload =
FileUpload.wrap(Document.get().getElementById("foo"));
@@ -58,6 +109,9 @@
assertExistsAndAttached(upload);
}
+ /**
+ * Tests {...@link FormPanel#wrap(Element)}.
+ */
public void testFormPanel() {
ensureDiv().setInnerHTML("<form id='foo'></form>");
FormPanel formPanel =
FormPanel.wrap(Document.get().getElementById("foo"));
@@ -65,6 +119,9 @@
assertExistsAndAttached(formPanel);
}
+ /**
+ * Tests {...@link Frame#wrap(Element)}.
+ */
public void testFrame() {
ensureDiv().setInnerHTML("<iframe id='foo'>myFrame</iframe>");
Frame frame = Frame.wrap(Document.get().getElementById("foo"));
@@ -72,6 +129,9 @@
assertExistsAndAttached(frame);
}
+ /**
+ * Tests {...@link Hidden#wrap(Element)}.
+ */
public void testHidden() {
ensureDiv().setInnerHTML("<input type='hidden' id='foo'></input>");
Hidden hidden = Hidden.wrap(Document.get().getElementById("foo"));
@@ -79,6 +139,9 @@
assertExistsAndAttached(hidden);
}
+ /**
+ * Tests {...@link HTML#wrap(Element)}.
+ */
public void testHTML() {
ensureDiv().setInnerHTML("<div id='foo'>myHTML</div>");
HTML html = HTML.wrap(Document.get().getElementById("foo"));
@@ -87,6 +150,9 @@
assertEquals("myHTML", html.getHTML());
}
+ /**
+ * Tests {...@link Image#wrap(Element)}.
+ */
public void testImage() {
ensureDiv().setInnerHTML("<img id='foo' src='" + IMG_URL + "'>");
Image image = Image.wrap(Document.get().getElementById("foo"));
@@ -95,6 +161,9 @@
assertEquals(IMG_URL, image.getUrl());
}
+ /**
+ * Tests {...@link InlineHTML#wrap(Element)}.
+ */
public void testInlineHTML() {
ensureDiv().setInnerHTML("<span id='foo'>myInlineHTML</span>");
InlineHTML html =
InlineHTML.wrap(Document.get().getElementById("foo"));
@@ -103,6 +172,9 @@
assertEquals("myInlineHTML", html.getHTML());
}
+ /**
+ * Tests {...@link InlineLabel#wrap(Element)}.
+ */
public void testInlineLabel() {
ensureDiv().setInnerHTML("<span id='foo'>myInlineLabel</span>");
InlineLabel label =
InlineLabel.wrap(Document.get().getElementById("foo"));
@@ -111,6 +183,9 @@
assertEquals("myInlineLabel", label.getText());
}
+ /**
+ * Tests {...@link Label#wrap(Element)}.
+ */
public void testLabel() {
ensureDiv().setInnerHTML("<div id='foo'>myLabel</div>");
Label label = Label.wrap(Document.get().getElementById("foo"));
@@ -119,6 +194,9 @@
assertEquals("myLabel", label.getText());
}
+ /**
+ * Tests {...@link ListBox#wrap(Element)}.
+ */
public void testListBox() {
ensureDiv().setInnerHTML("<select id='foo'></select>");
ListBox listBox = ListBox.wrap(Document.get().getElementById("foo"));
@@ -126,6 +204,41 @@
assertExistsAndAttached(listBox);
}
+ /**
+ * Tests that all widgets passed to
+ * {...@link RootPanel#detachOnWindowClose(Widget)} are cleaned up properly
when
+ * the window is unloaded, regardless of whether their associated
elements are
+ * still in the DOM or not.
+ */
+ public void testOnUnloadDetachesAllWidgets() {
+ // Testing hosted-mode-only assertion.
+ if (!GWT.isScript()) {
+ ensureDiv().setInnerHTML(
+ "<a id='foo' href='" + TEST_URL + "'>myAnchor</a>"
+ + "<a id='bar' href='" + TEST_URL + "'>myOtherAnchor</a>");
+
+ // Wrap one widget that will be left in the DOM normally.
+ Element fooElem = Document.get().getElementById("foo");
+ Anchor fooAnchor = Anchor.wrap(fooElem);
+
+ // Wrap another widget and remove its element from the DOM.
+ Element barElem = Document.get().getElementById("bar");
+ Anchor barAnchor = Anchor.wrap(barElem);
+ barElem.getParentElement().removeChild(barElem);
+
+ // Fake an unload by telling the RootPanel to go ahead and detach all
+ // of its widgets.
+ RootPanel.detachWidgets();
+
+ // Now make sure that both widgets were detached properly.
+ assertFalse("fooAnchor should have been detached",
fooAnchor.isAttached());
+ assertFalse("barAnchor should have been detached",
barAnchor.isAttached());
+ }
+ }
+
+ /**
+ * Tests {...@link PasswordTextBox#wrap(Element)}.
+ */
public void testPasswordTextBox() {
ensureDiv().setInnerHTML("<input type='password' id='foo'></input>");
PasswordTextBox textBox =
PasswordTextBox.wrap(Document.get().getElementById(
@@ -134,6 +247,9 @@
assertExistsAndAttached(textBox);
}
+ /**
+ * Tests {...@link SimpleCheckBox#wrap(Element)}.
+ */
public void testSimpleCheckBox() {
ensureDiv().setInnerHTML("<input type='checkbox' id='foo'></input>");
SimpleCheckBox checkBox =
SimpleCheckBox.wrap(Document.get().getElementById(
@@ -142,6 +258,9 @@
assertExistsAndAttached(checkBox);
}
+ /**
+ * Tests {...@link SimpleRadioButton#wrap(Element)}.
+ */
public void testSimpleRadioButton() {
ensureDiv().setInnerHTML("<input type='radio' id='foo'></input>");
SimpleRadioButton radio =
SimpleRadioButton.wrap(Document.get().getElementById(
@@ -150,6 +269,9 @@
assertExistsAndAttached(radio);
}
+ /**
+ * Tests {...@link TextArea#wrap(Element)}.
+ */
public void testTextArea() {
ensureDiv().setInnerHTML("<textarea rows='1' cols='1'
id='foo'></textarea>");
TextArea textArea =
TextArea.wrap(Document.get().getElementById("foo"));
@@ -157,6 +279,9 @@
assertExistsAndAttached(textArea);
}
+ /**
+ * Tests {...@link TextBox#wrap(Element)}.
+ */
public void testTextBox() {
ensureDiv().setInnerHTML("<input type='text' id='foo'></input>");
TextBox textBox = TextBox.wrap(Document.get().getElementById("foo"));
@@ -164,40 +289,34 @@
assertExistsAndAttached(textBox);
}
- public void testDetachOnUnloadTwiceFails() {
+ /**
+ * Tests that wrapping an element that is already a child of an existing
+ * widget's element fails.
+ */
+ public void testWrappingChildElementFails() {
// Testing hosted-mode-only assertion.
if (!GWT.isScript()) {
try {
- // Trying to pass the same widget to RootPanel.detachOnUnload()
twice
- // should fail an assertion (the first call is implicit through
- // Anchor.wrap()).
- ensureDiv().setInnerHTML(
- "<a id='foo' href='" + TEST_URL + "'>myAnchor</a>");
- Anchor a = Anchor.wrap(Document.get().getElementById("foo")); //
pass
- RootPanel.detachOnWindowClose(a); // fail
- fail("Expected assertion failure calling detachOnLoad() twice");
- } catch (AssertionError e) {
- }
- }
- }
-
- public void testDetachNowTwiceFails() {
- // Testing hosted-mode-only assertion.
- if (!GWT.isScript()) {
- try {
- // Trying to pass the same widget to RootPanel.detachNow() twice
- // should fail an assertion.
- ensureDiv().setInnerHTML(
- "<a id='foo' href='" + TEST_URL + "'>myAnchor</a>");
- Anchor a = Anchor.wrap(Document.get().getElementById("foo"));
- RootPanel.detachNow(a); // pass
- RootPanel.detachNow(a); // fail
- fail("Expected assertion failure calling detachNow() twice");
+ // Create a panel that contains HTML with a unique id, which we're
+ // going to try and wrap below.
+ FlowPanel p = new FlowPanel();
+ RootPanel.get().add(p);
+ p.add(new HTML("<a id='twcef_id'>foo</a>"));
+
+ // Get the element and try to wrap it.
+ Element unwrappableElement =
Document.get().getElementById("twcef_id");
+ Anchor.wrap(unwrappableElement);
+ fail("Attempting to wrap the above element should have failed.");
} catch (AssertionError e) {
+ // Expected error.
}
}
}
+ /**
+ * Tests that wrap() may only be called on elements that are already
attached
+ * to the DOM.
+ */
public void testWrapUnattachedFails() {
// Testing hosted-mode-only assertion.
if (!GWT.isScript()) {
@@ -209,28 +328,6 @@
AnchorElement aElem = Document.get().createAnchorElement();
Anchor.wrap(aElem);
fail("Expected assertion failure wrapping unattached element");
- } catch (AssertionError e) {
- }
- }
- }
-
- public void testOnUnloadAssertions() {
- // Testing hosted-mode-only assertion.
- if (!GWT.isScript()) {
- try {
- // When a wrap()ed element is detached from the document without
being
- // properly unwrapped, there will be an assertion to catch this
run on
- // unload.
- ensureDiv().setInnerHTML(
- "<a id='foo' href='" + TEST_URL + "'>myAnchor</a>");
- Element aElem = Document.get().getElementById("foo");
- Anchor.wrap(aElem);
- aElem.getParentElement().removeChild(aElem);
-
- // Fake an unload by telling the RootPanel to go ahead and detach
all
- // of its widgets.
- RootPanel.detachWidgets();
- fail("Assertion expected for orphaned wrap()ed widgets");
} catch (AssertionError e) {
}
}
Modified:
releases/1.6/user/test/com/google/gwt/user/client/ui/HTMLPanelTest.java
==============================================================================
--- releases/1.6/user/test/com/google/gwt/user/client/ui/HTMLPanelTest.java
(original)
+++ releases/1.6/user/test/com/google/gwt/user/client/ui/HTMLPanelTest.java
Fri Mar 6 07:55:47 2009
@@ -15,10 +15,9 @@
*/
package com.google.gwt.user.client.ui;
+import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.Node;
import com.google.gwt.junit.client.GWTTestCase;
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Element;
/**
* Tests the HTMLPanel widget.
@@ -44,10 +43,8 @@
p.add(labelA, "a");
p.add(labelB, "b");
// Ensure that both Label's have the correct parent.
- assertEquals("a", DOM.getElementAttribute(
- DOM.getParent(labelA.getElement()), "id"));
- assertEquals("b", DOM.getElementAttribute(
- DOM.getParent(labelB.getElement()), "id"));
+ assertEquals("a", labelA.getElement().getParentElement().getId());
+ assertEquals("b", labelB.getElement().getParentElement().getId());
}
/**
@@ -70,9 +67,12 @@
// If all goes well, the HTMLPanel's element should still be properly
// connected to the SimplePanel's element.
- assertTrue(DOM.isOrHasChild(sp.getElement(), p.getElement()));
+ assertTrue(sp.getElement().isOrHasChild(p.getElement()));
}
+ /**
+ * Tests child attachment order using {...@link HasWidgetsTester}.
+ */
public void testAttachDetachOrder() {
HTMLPanel p = new HTMLPanel("<div id='w00t'></div>");
HasWidgetsTester.testAll(p, new Adder());
@@ -96,9 +96,9 @@
hp.add(new Button("foo"), "foo");
- assertTrue(DOM.isOrHasChild(fp.getElement(), hp.getElement()));
- assertTrue(DOM.getNextSibling(ba.getElement()) == hp.getElement());
- assertTrue(DOM.getNextSibling(hp.getElement()) == bb.getElement());
+ assertTrue(fp.getElement().isOrHasChild(hp.getElement()));
+ assertTrue(ba.getElement().getNextSibling() == hp.getElement());
+ assertTrue(hp.getElement().getNextSibling() == bb.getElement());
}
/**
@@ -119,6 +119,10 @@
assertEquals("bar", next.getNodeValue());
}
+ /**
+ * Ensure that {...@link HTMLPanel#getElementById(String)} behaves properly
in
+ * both attached and unattached states.
+ */
public void testGetElementById() {
HTMLPanel hp = new HTMLPanel("foo<div id='toFind'>bar</div>baz");
@@ -129,5 +133,28 @@
// Make sure we got the same element in both cases.
assertEquals(elem0, elem1);
+ }
+
+ /**
+ * Tests that the HTMLPanel's element is not moved from its original
location
+ * when {...@link HTMLPanel#add(Widget, String)} is called on it while it is
+ * unattached.
+ */
+ public void testElementIsUnmoved() {
+ HTMLPanel unattached = new HTMLPanel("<div id='unattached'></div>");
+ HTMLPanel attached = new HTMLPanel("<div id='attached'></div>");
+
+ RootPanel.get().add(attached);
+
+ Element unattachedParentElem =
unattached.getElement().getParentElement();
+ Element attachedParentElem = attached.getElement().getParentElement();
+
+ unattached.add(new Button("unattached"), "unattached");
+ attached.add(new Button("attached"), "attached");
+
+ assertEquals("Unattached's parent element should be unaffected",
+ unattachedParentElem, unattached.getElement().getParentElement());
+ assertEquals("Unattached's parent element should be unaffected",
+ attachedParentElem, attached.getElement().getParentElement());
}
}
Added:
releases/1.6/user/test/com/google/gwt/user/client/ui/RootPanelTest.java
==============================================================================
--- (empty file)
+++ releases/1.6/user/test/com/google/gwt/user/client/ui/RootPanelTest.java
Fri Mar 6 07:55:47 2009
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may
not
+ * use this file except in compliance with the License. You may obtain a
copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
under
+ * the License.
+ */
+package com.google.gwt.user.client.ui;
+
+import com.google.gwt.dom.client.DivElement;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.junit.client.GWTTestCase;
+
+/**
+ * Tests {...@link RootPanel}.
+ */
+public class RootPanelTest extends GWTTestCase {
+
+ @Override
+ public String getModuleName() {
+ return "com.google.gwt.user.User";
+ }
+
+ /**
+ * Ensures that {...@link RootPanel#get(String)} behaves properly.
+ */
+ public void testGetById() {
+ Document doc = Document.get();
+ DivElement div = doc.createDivElement();
+ doc.getBody().appendChild(div);
+ div.setInnerHTML("<div id='a'></div><div id='b'></div>");
+
+ // You should get the same RootPanel for subsequent calls to get()
with the
+ // same id. But you should get *different* RootPanels for calls with
+ // different ids.
+ RootPanel aRoot = RootPanel.get("a");
+ RootPanel bRoot = RootPanel.get("b");
+
+ assertSame(
+ "RootPanel.get() should return the same instancefor the same id",
+ aRoot, RootPanel.get("a"));
+ assertSame(
+ "RootPanel.get() should return the same instancefor the same id",
+ bRoot, RootPanel.get("b"));
+ assertNotSame("RootPanels a and b should be different", aRoot, bRoot);
+
+ // If a RootPanel's element is replaced in the DOM, you should get a
+ // new RootPanel instance if you ask for it again (see issue 1937).
+ Element aElem = doc.getElementById("a");
+ Element newAElem = doc.createDivElement();
+ newAElem.setId("a");
+ aElem.getParentElement().replaceChild(newAElem, aElem);
+
+ RootPanel newARoot = RootPanel.get("a");
+ assertNotSame("New RootPanel should not be same as old", newARoot,
aRoot);
+ }
+}
Modified:
releases/1.6/user/test/com/google/gwt/user/client/ui/SuggestBoxTest.java
==============================================================================
---
releases/1.6/user/test/com/google/gwt/user/client/ui/SuggestBoxTest.java
(original)
+++
releases/1.6/user/test/com/google/gwt/user/client/ui/SuggestBoxTest.java
Fri Mar 6 07:55:47 2009
@@ -15,6 +15,8 @@
*/
package com.google.gwt.user.client.ui;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.Element;
import com.google.gwt.junit.client.GWTTestCase;
import java.util.Arrays;
@@ -110,7 +112,31 @@
RootPanel.get().clear();
}
+ public void testWrapUsingStaticWrapMethod() {
+ Element wrapper = Document.get().createTextInputElement();
+ RootPanel.get().getElement().appendChild(wrapper);
+
+ // Use direct wrap method from suggest box.
+ SuggestBox box = SuggestBox.wrap(createOracle(), wrapper);
+ assertTrue(box.isAttached());
+ assertTrue(box.getWidget().getParent() == box);
+ }
+
+ public void testWrapUsingComposite() {
+ // Ensure we can use this with normal composites
+ Element wrapper = Document.get().createTextInputElement();
+ RootPanel.get().getElement().appendChild(wrapper);
+ TextBox b = TextBox.wrap(wrapper);
+ SuggestBox box = new SuggestBox(createOracle(), b);
+ assertTrue(b.getParent() == box);
+ }
+
protected SuggestBox createSuggestBox() {
+ MultiWordSuggestOracle oracle = createOracle();
+ return new SuggestBox(oracle);
+ }
+
+ private MultiWordSuggestOracle createOracle() {
MultiWordSuggestOracle oracle = new MultiWordSuggestOracle();
oracle.add("test");
oracle.add("test1");
@@ -118,6 +144,6 @@
oracle.add("test3");
oracle.add("test4");
oracle.add("john");
- return new SuggestBox(oracle);
+ return oracle;
}
}
--~--~---------~--~----~------------~-------~--~----~
http://groups.google.com/group/Google-Web-Toolkit-Contributors
-~----------~----~----~----~------~----~------~--~---