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

Reply via email to