| /* |
| * 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; |
| 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. |
| * <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 { |
| |
| static { |
| declareShim(); |
| } |
| |
| public static PotentialElement as(Element e) { |
| assert isPotential(e); |
| return (PotentialElement) e; |
| } |
| |
| /** |
| * Builds a new PotentialElement with the tag name set to "div". |
| * |
| * @see #build(UIObject,String) |
| */ |
| public static PotentialElement build(UIObject o) { |
| return build(o, "div"); |
| } |
| |
| /** |
| * 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, String tagName) /*-{ |
| 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); |
| } 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 native JavaScriptObject buildResolveCallback(UIObject resolver) /*-{ |
| return function() { |
| this.__gwt_resolve = @com.google.gwt.user.client.ui.PotentialElement::cannotResolveTwice(); |
| return resolver.@com.google.gwt.user.client.ui.UIObject::resolvePotentialElement()(); |
| }; |
| }-*/; |
| |
| private static final native void cannotResolveTwice() /*-{ |
| 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() { |
| } |
| |
| final native Element setResolver(UIObject resolver) /*-{ |
| this.__gwt_resolve = @com.google.gwt.user.client.ui.PotentialElement::buildResolveCallback(Lcom/google/gwt/user/client/ui/UIObject;)(resolver); |
| }-*/; |
| |
| /** |
| * 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 |
| * on the associated UIObject. |
| */ |
| private native Element resolve() /*-{ |
| return this.__gwt_resolve ? this.__gwt_resolve() : this; |
| }-*/; |
| } |