Re-submit: Iterating over all attributed when realizing PotentialElements. Review at http://gwt-code-reviews.appspot.com/1573804 Review by: rjrjr@google.com git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@10709 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/user/client/ui/PotentialElement.java b/user/src/com/google/gwt/user/client/ui/PotentialElement.java index f8abf74..83d06d9 100644 --- a/user/src/com/google/gwt/user/client/ui/PotentialElement.java +++ b/user/src/com/google/gwt/user/client/ui/PotentialElement.java
@@ -17,6 +17,8 @@ import com.google.gwt.core.client.JavaScriptObject; import com.google.gwt.dom.client.Element; +import com.google.gwt.dom.builder.shared.HtmlElementBuilder; +import com.google.gwt.dom.builder.shared.HtmlBuilderFactory; /** * EXPERIMENTAL and subject to change. Do not use this in production code. @@ -37,6 +39,10 @@ */ public class PotentialElement extends Element { + static { + declareShim(); + } + public static PotentialElement as(Element e) { assert isPotential(e); return (PotentialElement) e; @@ -58,34 +64,34 @@ * that is needed. */ public static native PotentialElement build(UIObject o, String tagName) /*-{ - 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: {}, - tagName: tagName, - __gwt_resolve: @com.google.gwt.user.client.ui.PotentialElement::buildResolveCallback(Lcom/google/gwt/user/client/ui/UIObject;)(o), - title: '' - }); + var el = new $wnd.GwtPotentialElementShim(); + el.tagName = tagName; + el.__gwt_resolve = @com.google.gwt.user.client.ui.PotentialElement::buildResolveCallback(Lcom/google/gwt/user/client/ui/UIObject;)(o); + return @com.google.gwt.dom.client.Element::as(Lcom/google/gwt/core/client/JavaScriptObject;)(el); }-*/; + /** + * Creates an {@link HtmlElementBuilder} instance inheriting all attributes + * set for the given PotentialElement. + * + * @param potentialElement assumed to be a PotentialElement, used as basis for + * the builder + * @return a propertly configured {@link HtmlElementBuilder} instance + */ + public static HtmlElementBuilder createBuilderFor(Element potentialElement) { + PotentialElement el = PotentialElement.as(potentialElement); + HtmlElementBuilder builder = HtmlBuilderFactory.get().trustedCreate( + el.getTagName()); + el.mergeInto(builder); + return builder; + } + + /** + * Tests whether a given {@link JavaScriptObject} represents a PotentialElement. + * + * @param o the {@link JavaScriptObject} to be tested + * @return true if the given object is a PotentialElement instance + */ public static native boolean isPotential(JavaScriptObject o) /*-{ try { return (!!o) && (!!o.__gwt_resolve); @@ -117,6 +123,35 @@ throw "A PotentialElement cannot be resolved twice."; }-*/; + private static final native void declareShim() /*-{ + var shim = function() {}; + shim.prototype = { + 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: {}, + title: '' + }; + $wnd.GwtPotentialElementShim = shim; + }-*/; + protected PotentialElement() { } @@ -125,6 +160,54 @@ }-*/; /** + * Copy only the fields that have actually changed from the values in the shim + * prototype. Do this by severing the __proto__ link, allowing us to iterate + * only on the fields set in this specific instance. + */ + private native void mergeInto(HtmlElementBuilder builder) /*-{ + var savedProto = this.__proto__; + var tagName = this.tagName; + var gwtResolve = this.__gwt_resolve; + var className = this.className; + + try { + this.__proto__ = null; + this.tagName = null; + this.__gwt_resolve = null; + + // className needs special treatment because the actual HTML attribute is + // called "class" and not "className". + if (this.className) { + builder.@com.google.gwt.dom.builder.shared.ElementBuilder::className(Ljava/lang/String;)( + this.className); + this.className = null; + } + + // Iterate over all attributes, and copy them to the ElementBuilder. + // TODO(rdcastro): Deal with the "style" attribute. + for (attr in this) { + if (!this[attr]) { + continue; + } + if (typeof this[attr] == 'number') { + builder.@com.google.gwt.dom.builder.shared.ElementBuilder::attribute(Ljava/lang/String;I)( + attr, this[attr]); + } else if (typeof this[attr] == 'string') { + builder.@com.google.gwt.dom.builder.shared.ElementBuilder::attribute(Ljava/lang/String;Ljava/lang/String;)( + attr, this[attr]); + } + } + } finally { + this.__proto__ = savedProto; + if (className) { + this.className = className; + } + this.__gwt_resolve = gwtResolve; + this.tagName = tagName; + } + }-*/; + + /** * 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
diff --git a/user/src/com/google/gwt/user/client/ui/RenderablePanel.java b/user/src/com/google/gwt/user/client/ui/RenderablePanel.java index 777e289..747c049 100644 --- a/user/src/com/google/gwt/user/client/ui/RenderablePanel.java +++ b/user/src/com/google/gwt/user/client/ui/RenderablePanel.java
@@ -15,8 +15,7 @@ */ package com.google.gwt.user.client.ui; -import com.google.gwt.dom.builder.shared.HtmlDivBuilder; -import com.google.gwt.dom.builder.shared.HtmlBuilderFactory; +import com.google.gwt.dom.builder.shared.HtmlElementBuilder; import com.google.gwt.dom.client.Document; import com.google.gwt.dom.client.Element; import com.google.gwt.safehtml.shared.SafeHtml; @@ -189,18 +188,12 @@ @Override public SafeHtml render(RenderableStamper stamper) { - String styleName = getStyleName(); + HtmlElementBuilder builder = PotentialElement.createBuilderFor(getElement()); + stamper.stamp(builder); + builder.html(getInnerHtml()).end(); - HtmlDivBuilder divBuilder = HtmlBuilderFactory.get() - .createDivBuilder(); - if (styleName != null) { - divBuilder.className(styleName); - styleName = null; - } - stamper.stamp(divBuilder); - divBuilder.html(getInnerHtml()).end(); - - return divBuilder.asSafeHtml(); + SafeHtml returnValue = builder.asSafeHtml(); + return returnValue; } @Override @@ -236,6 +229,8 @@ * that may have been added to the panel. */ private void buildAndInitDivContainer() { + // TODO(rdcastro): Use the same technique as in render() above. + // Build the div that'll container the panel's HTML. Element element = Document.get().createDivElement(); element.setInnerHTML(getInnerHtml().asString());