Revision: 10279
Author: rdcas...@google.com
Date: Mon Jun 6 04:09:34 2011
Log: Introduce PotentialElement to help with the implementation of
IsRenderable widgets.
Review at http://gwt-code-reviews.appspot.com/1450810
http://code.google.com/p/google-web-toolkit/source/detail?r=10279
Added:
/trunk/user/src/com/google/gwt/user/client/ui/PotentialElement.java
Modified:
/trunk/user/src/com/google/gwt/user/client/DOM.java
/trunk/user/src/com/google/gwt/user/client/ui/RenderablePanel.java
/trunk/user/src/com/google/gwt/user/client/ui/UIObject.java
=======================================
--- /dev/null
+++ /trunk/user/src/com/google/gwt/user/client/ui/PotentialElement.java Mon
Jun 6 04:09:34 2011
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2011 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.core.client.JavaScriptObject;
+import com.google.gwt.dom.client.Element;
+
+/**
+ * EXPERIMENTAL and subject to change. Do not use this in production code.
+ * <p>
+ * A simple {@link Element} implementation (<strong>not</strong> an actual
dom
+ * object) that can serve as stand in to be used by {@link IsRenderable}
widgets
+ * before they are fully built. For example, it can accumulate simple
set*()
+ * values to be used when the widget is actually ready to render. Thus,
most
+ * {@link IsRenderable} widget code can be written without taking into
account
+ * whether or not the widget has yet been rendered.
+ * <p>
+ * {@link DOM#appendChild} is aware of PotentialElement, and calls its
+ * resolve() method. This triggers a call to
+ * {@link UIObject#resolvePotentialElement()}, which widgets can customize
+ * to get a real {@link Element} in place at the last moment.
+ *
+ * TODO(rdcastro): Cover all unsupported methods with helpful error
messages.
+ */
+public class PotentialElement extends Element {
+
+ /**
+ * Builds a new PotentialElement. This element keeps track of the
+ * {@link UIObject} so that it can call
+ * {@link UIObject#resolvePotentialElement} to get a real element when
+ * that is needed.
+ */
+ public static native PotentialElement build(UIObject o) /*-{
+ return
@com.google.gwt.dom.client.Element::as(Lcom/google/gwt/core/client/JavaScriptObject;)({
+ className: '',
+ clientHeight: 0,
+ clientWidth: 0,
+ dir: '',
+ getAttribute: function(name, value) {
+ return this[name];
+ },
+ href: '',
+ id: '',
+ lang: '',
+ // should be @com.google.gwt.dom.client.Node.ELEMENT_MODE, but the
compiler
+ // doesn't like that.
+ nodeType: 1,
+ removeAttribute: function(name, value) {
+ this[name] = undefined;
+ },
+ setAttribute: function(name, value) {
+ this[name] = value;
+ },
+ src: '',
+ style: {},
+ __gwt_resolve: function() {
+ this.__gwt_resolve =
@com.google.gwt.user.client.ui.PotentialElement::cannotResolveTwice();
+ return
o...@com.google.gwt.user.client.ui.UIObject::resolvePotentialElement()();
+ },
+ title: ''
+ });
+ }-*/;
+
+ public static native boolean isPotential(JavaScriptObject o) /*-{
+ try {
+ return (!!o) && (!!o.__gwt_resolve);
+ } catch (e) {
+ return false;
+ }
+ }-*/;
+
+ /**
+ * If given a PotentialElement, returns the real Element to be
+ * built from it. Otherwise returns the given Element itself.
+ * <p>
+ * Note that a PotentialElement can only be resolved once.
+ * Making repeated calls to this method with the same PotentialElement
+ * is an error.
+ */
+ public static Element resolve(Element maybePotential) {
+ return maybePotential.<PotentialElement>cast().resolve();
+ }
+
+ private static final native void cannotResolveTwice() /*-{
+ throw "A PotentialElement cannot be resolved twice.";
+ }-*/;
+
+ protected PotentialElement() {
+ }
+
+ /**
+ * Calls the <code>__gwt_resolve</code> method on the underlying
+ * JavaScript object if it exists. On objects created via {@link
#build}, this
+ * method is a call to the {@link UIObject#resolvePotentialElement}
method
+ * on the associated UIObject.
+ */
+ private native Element resolve() /*-{
+ return this.__gwt_resolve ? this.__gwt_resolve() : this;
+ }-*/;
+}
=======================================
--- /trunk/user/src/com/google/gwt/user/client/DOM.java Wed Mar 23 13:32:42
2011
+++ /trunk/user/src/com/google/gwt/user/client/DOM.java Mon Jun 6 04:09:34
2011
@@ -21,6 +21,7 @@
import com.google.gwt.dom.client.OptionElement;
import com.google.gwt.dom.client.SelectElement;
import com.google.gwt.user.client.impl.DOMImpl;
+import com.google.gwt.user.client.ui.PotentialElement;
/**
* This class provides a set of static methods that allow you to
manipulate the
@@ -52,12 +53,18 @@
/**
* Appends one element to another's list of children.
- *
+ * If the child element is a {@link PotentialElement}, it is first
resolved
+ * {@see PotentialElement#resolve(Element)}.
+ *
* @param parent the parent element
* @param child its new child
*/
public static void appendChild(Element parent, Element child) {
- parent.appendChild(child);
+ assert !PotentialElement.isPotential(parent) : "Cannot append to a
PotentialElement";
+
+ // If child isn't a PotentialElement, resolve() returns
+ // the Element itself.
+ parent.appendChild(PotentialElement.resolve(child));
}
/**
=======================================
--- /trunk/user/src/com/google/gwt/user/client/ui/RenderablePanel.java Tue
May 24 10:37:15 2011
+++ /trunk/user/src/com/google/gwt/user/client/ui/RenderablePanel.java Mon
Jun 6 04:09:34 2011
@@ -63,7 +63,6 @@
public Command detachedInitializationCallback = null;
protected SafeHtml html = null;
- private String styleName = null;
/**
* Creates an HTML panel with the specified HTML contents inside a DIV
@@ -87,6 +86,7 @@
*/
public RenderablePanel(SafeHtml safeHtml) {
this.html = safeHtml;
+ setElement(PotentialElement.build(this));
}
/**
@@ -136,22 +136,6 @@
com.google.gwt.user.client.Element toReplace) {
this.addAndReplaceElement(widget.asWidget(), toReplace);
}
-
- @Override
- public com.google.gwt.user.client.Element getElement() {
- if (!isFullyInitialized()) {
- // In case we haven't finished initialization yet, finish it now.
- buildAndInitDivContainer();
- html = null;
-
- // We might have to add a style that has been previously set.
- if (styleName != null) {
- super.setStyleName(styleName);
- styleName = null;
- }
- }
- return super.getElement();
- }
/**
* Adopts the given, but doesn't change anything about its DOM element.
@@ -187,6 +171,7 @@
@Override
public void render(String id, SafeHtmlBuilder builder) {
+ String styleName = getStyleName();
if (styleName != null) {
builder.append(TEMPLATE.renderWithIdAndClass(id, styleName,
getInnerHtml()));
styleName = null;
@@ -196,14 +181,10 @@
}
@Override
- public void setStyleName(String styleName) {
- if (!isFullyInitialized()) {
- // If we haven't built the actual HTML element yet, we save the style
- // to apply later on.
- this.styleName = styleName;
- } else {
- super.setStyleName(styleName);
- }
+ public Element resolvePotentialElement() {
+ buildAndInitDivContainer();
+ html = null;
+ return getElement();
}
@Override
@@ -215,9 +196,8 @@
* build the whole tree at once.
*/
throw new IllegalStateException(
- "wrapElement() cannot be called twice, or after a call to
getElement()"
- + "has forced the widget to render itself (e.g. after adding it
to a"
- + "panel)");
+ "wrapElement() cannot be called twice, or after forcing the
widget to"
+ + " render itself (e.g. after adding it to a panel)");
}
setElement(element);
@@ -252,6 +232,14 @@
// Build the div that'll container the panel's HTML.
Element element = Document.get().createDivElement();
element.setInnerHTML(getInnerHtml().asString());
+
+ // TODO(rdcastro): Implement something like
+ // element.mergeFrom(getElement());
+ String styleName = getStyleName();
+ if (styleName != null) {
+ element.setClassName(styleName);
+ }
+
setElement(element);
// If there's any wrap callback to call, we have to attach the div
before
=======================================
--- /trunk/user/src/com/google/gwt/user/client/ui/UIObject.java Wed Mar 23
13:32:42 2011
+++ /trunk/user/src/com/google/gwt/user/client/ui/UIObject.java Mon Jun 6
04:09:34 2011
@@ -840,6 +840,34 @@
protected void onEnsureDebugId(String baseID) {
ensureDebugId(getElement(), "", baseID);
}
+
+ /**
+ * EXPERIMENTAL and subject to change. Do not use this in production
code.
+ * <p>
+ * To be overridden by {@link IsRenderable} subclasses that initialize
+ * themselves by by calling
+ * <code>setElement(PotentialElement.build(this))</code>.
+ * <p>
+ * The receiver must:
+ * <ul>
+ * <li> create a real {@link Element} to replace its {@link
PotentialElement}
+ * <li> call {@link #setElement()} with the new Element
+ * <li> and return the new Element
+ * </ul>
+ * <p>
+ * This method is called when the receiver's element is about to be
+ * added to a parent node, as a side effect of {@link DOM#appendChild}.
+ * <p>
+ * Note that this method is normally called only on the top element
+ * of an IsRenderable tree. Children instead will receive {@link
+ * IsRenderable#render} and {@link IsRenderable#wrap}.
+ *
+ * @see PotentialElement
+ * @see IsRenderable
+ */
+ protected Element resolvePotentialElement() {
+ throw new UnsupportedOperationException();
+ }
/**
* Sets this object's browser element. UIObject subclasses must call this
@@ -863,7 +891,7 @@
* @param elem the object's element
*/
protected void setElement(com.google.gwt.user.client.Element elem) {
- assert (element == null) : SETELEMENT_TWICE_ERROR;
+ assert (element == null || PotentialElement.isPotential(element)) :
SETELEMENT_TWICE_ERROR;
this.element = elem;
}
--
http://groups.google.com/group/Google-Web-Toolkit-Contributors