Java tutorial
/* * Copyright 2008 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 java.util.Locale; import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.JavaScriptObject; import com.google.gwt.debug.client.DebugInfo; import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.ElementRemote; import com.google.gwt.user.client.DOM; import cc.alcina.framework.common.client.util.Ax; import cc.alcina.framework.common.client.util.CommonUtils; /** * The superclass for all user-interface objects. It simply wraps a DOM element, * and cannot receive events. Most interesting user-interface classes derive * from {@link com.google.gwt.user.client.ui.Widget}. * * <h3>Styling With CSS</h3> * <p> * All <code>UIObject</code> objects can be styled using CSS. Style names that * are specified programmatically in Java source are implicitly associated with * CSS style rules. In terms of HTML and CSS, a GWT style name is the element's * CSS "class". By convention, GWT style names are of the form * <code>[project]-[widget]</code>. * </p> * * <p> * For example, the {@link Button} widget has the style name * <code>gwt-Button</code>, meaning that within the <code>Button</code> * constructor, the following call occurs: * * <pre class="code"> * setStyleName("gwt-Button"); * </pre> * * A corresponding CSS style rule can then be written as follows: * * <pre class="code"> * // Example of how you might choose to style a Button widget * .gwt-Button { * background-color: yellow; * color: black; * font-size: 24pt; * } * </pre> * * Note the dot prefix in the CSS style rule. This syntax is called a * <a href="http://www.w3.org/TR/REC-CSS2/selector.html#class-html">CSS class * selector</a>. * </p> * * <h3>Style Name Specifics</h3> * <p> * Every <code>UIObject</code> has a <i>primary style name</i> that identifies * the key CSS style rule that should always be applied to it. Use * {@link #setStylePrimaryName(String)} to specify an object's primary style * name. In most cases, the primary style name is set in a widget's constructor * and never changes again during execution. In the case that no primary style * name is specified, it defaults to the first style name that is added. * </p> * * <p> * More complex styling behavior can be achieved by manipulating an object's * <i>secondary style names</i>. Secondary style names can be added and removed * using {@link #addStyleName(String)}, {@link #removeStyleName(String)}, or * {@link #setStyleName(String, boolean)}. The purpose of secondary style names * is to associate a variety of CSS style rules over time as an object * progresses through different visual states. * </p> * * <p> * There is an important special formulation of secondary style names called * <i>dependent style names</i>. A dependent style name is a secondary style * name prefixed with the primary style name of the widget itself. See * {@link #addStyleName(String)} for details. * </p> * * <h3>Use in UiBinder Templates</h3> * <p> * Setter methods that follow JavaBean property conventions are exposed as * attributes in {@link com.google.gwt.uibinder.client.UiBinder UiBinder} * templates. For example, because UiObject implements {@link #setWidth(String)} * you can set the width of any widget like so: * * <pre> * <g:Label width='15em'>Hello there</g:Label> * </pre> * * Generally speaking, values are parsed as if they were Java literals, so * methods like {@link #setVisible(boolean)} are also available: * * <pre> * <g:Label width='15em' visible='false'>Hello there</g:Label> * </pre> * * Enum properties work this way too. Imagine a Bagel widget with a handy Type * enum and a setType(Type) method: * * <pre> * enum Type { poppy, sesame, raisin, jalapeno } * * <my:Bagel type='poppy' /> * </pre> * * There is also special case handling for two common method signatures, * <code>(int, int)</code> and <code>(double, {@link * com.google.gwt.dom.client.Style.Unit Unit})</code> * * <pre> * <g:Label pixelSize='100, 100'>Hello there</g:Label> * </pre> * * Finally, a few UiObject methods get special handling. The debug id (see * {@link #ensureDebugId}) of any UiObject can be set via the * <code>debugId</code> attribute, and addtional style names and dependent style * names can be set with the <code>addStyleNames</code> and * <code>addStyleDependentNames</code> attributes. * * <pre> * <g:Label debugId='helloLabel' * addStyleNames='pretty rounded big'>Hello there</g:Label> * </pre> * * Style names can be space or comma separated. */ public abstract class UIObject implements HasVisibility { /** * Stores a regular expression object to extract float values from the * leading portion of an input string. */ private static JavaScriptObject numberRegex; /* * WARNING: For historical reasons, there are two Element classes being used * in this code. The dom.Element (com.google.gwt.dom.client.Element) class * is explicitly imported, while user.Element * (com.google.gwt.user.client.Element) is fully-qualified in the code. * * All new methods should use dom.Element, because user.Element extends it * but adds no methods. */ public static final String DEBUG_ID_PREFIX = DebugInfo.DEFAULT_DEBUG_ID_PREFIX; static final String MISSING_ELEMENT_ERROR = "This UIObject's element is not set; " + "you may be missing a call to either Composite.initWidget() or " + "UIObject.setElement()"; static final String SETELEMENT_TWICE_ERROR = "Element may only be set once"; private static DebugIdImpl debugIdImpl = GWT.create(DebugIdImpl.class); private static final String EMPTY_STYLENAME_MSG = "Style names cannot be empty"; private static final String NULL_HANDLE_MSG = "Null widget handle. If you " + "are creating a composite, ensure that initWidget() has been called."; /** * <p> * Ensure that elem has an ID property set, which allows it to integrate * with third-party libraries and test tools. If elem already has an ID, * this method WILL override it. The ID that you specify will be prefixed by * the static string {@link #DEBUG_ID_PREFIX}. * </p> * * <p> * This method will be compiled out and will have no effect unless you * inherit the DebugID module in your gwt.xml file by adding the following * line: * * <pre class="code"> * <inherits name="com.google.gwt.user.Debug"/> * </pre> * </p> * * @param elem * the target {@link Element} * @param id * the ID to set on the element */ public static void ensureDebugId(Element elem, String id) { ensureDebugId(elem, "", id); } /** * Returns whether the given element is visible in a way consistent with * {@link #setVisible(Element, boolean)}. * * <p> * Warning: implemented with a heuristic. The value returned takes into * account only the "display" style, ignoring CSS and Aria roles, thus may * not accurately reflect whether the element is actually visible in the * browser. * </p> */ public static boolean isVisible(Element elem) { return !elem.getStyle().getDisplay().equals("none"); } /** * Clears all of the element's style names and sets it to the given style. * * @param elem * the element whose style is to be modified * @param styleName * the new style name */ public static void setStyleName(Element elem, String styleName) { elem.setClassName(styleName); } /** * This convenience method adds or removes a style name for a given element. * This method is typically used to add and remove secondary style names, * but it can be used to remove primary stylenames as well, but that is not * recommended. See {@link #setStyleName(String)} for a description of how * primary and secondary style names are used. * * @param elem * the element whose style is to be modified * @param style * the secondary style name to be added or removed * @param add * <code>true</code> to add the given style, <code>false</code> * to remove it */ public static void setStyleName(Element elem, String style, boolean add) { if (elem == null) { throw new RuntimeException(NULL_HANDLE_MSG); } style = style.trim(); if (style.length() == 0) { throw new IllegalArgumentException(EMPTY_STYLENAME_MSG); } if (add) { elem.addClassName(style); } else { elem.removeClassName(style); } } /** * Shows or hides the given element. Also updates the "aria-hidden" * attribute by setting it to true when it is hidden and removing it when it * is shown. * * <p> * Warning: implemented with a heuristic based on the "display" style: * clears the "display" style to its default value if {@code visible} is * true, else forces the style to "none". If the "display" style is set to * "none" via CSS style sheets, the element remains invisible after a call * to {@code setVisible(elem, true)}. * </p> */ public static void setVisible(Element elem, boolean visible) { elem.getStyle().setProperty("display", visible ? "" : "none"); if (visible) { elem.removeAttribute("aria-hidden"); } else { elem.setAttribute("aria-hidden", "true"); } } /** * Replaces all instances of the primary style name with * newPrimaryStyleName. */ private static void updatePrimaryAndDependentStyleNames(Element elem, String newPrimaryStyle) { // if (elem.provideIsDom()) { // updatePrimaryAndDependentStyleNames0( // LocalDomBridge.remote(elem), newPrimaryStyle); // } else { // FIXME - probably delete the static String className = elem.getClassName(); if (Ax.isBlank(newPrimaryStyle)) { return; } String[] classes = className.split("\\s+"); String oldPrimaryStyle = classes[0]; int oldPrimaryStyleLen = oldPrimaryStyle.length(); classes[0] = newPrimaryStyle; for (int i = 1, n = classes.length; i < n; i++) { String name = classes[i]; if (name.length() > oldPrimaryStyleLen && name.charAt(oldPrimaryStyleLen) == '-' && name.indexOf(oldPrimaryStyle) == 0) { classes[i] = newPrimaryStyle + name.substring(oldPrimaryStyleLen); } } elem.setClassName(CommonUtils.join(classes, " ")); // } } /** * Replaces all instances of the primary style name with * newPrimaryStyleName. */ private static native void updatePrimaryAndDependentStyleNames0(ElementRemote elem, String newPrimaryStyle) /*-{ var classes = (elem.className || "").split(/\s+/); if (!classes) { return; } var oldPrimaryStyle = classes[0]; var oldPrimaryStyleLen = oldPrimaryStyle.length; classes[0] = newPrimaryStyle; for (var i = 1, n = classes.length; i < n; i++) { var name = classes[i]; if (name.length > oldPrimaryStyleLen && name.charAt(oldPrimaryStyleLen) == '-' && name.indexOf(oldPrimaryStyle) == 0) { classes[i] = newPrimaryStyle + name.substring(oldPrimaryStyleLen); } } elem.className = classes.join(" "); }-*/; /** * Set the debug id of a specific element. The id will be appended to the * end of the base debug id, with a dash separator. The base debug id is the * ID of the main element in this UIObject. * * @param elem * the element * @param baseID * the base ID used by the main element * @param id * the id to append to the base debug id */ protected static void ensureDebugId(Element elem, String baseID, String id) { debugIdImpl.ensureDebugId(elem, baseID, id); } /** * Gets all of the element's style names, as a space-separated list. * * @param elem * the element whose style is to be retrieved * @return the objects's space-separated style names */ protected static String getStyleName(Element elem) { return elem.getClassName(); } /** * Gets the element's primary style name. * * @param elem * the element whose primary style name is to be retrieved * @return the element's primary style name */ protected static String getStylePrimaryName(Element elem) { String fullClassName = getStyleName(elem); // The primary style name is always the first token of the full CSS // class // name. There can be no leading whitespace in the class name, so it's // not // necessary to trim() it. int spaceIdx = fullClassName.indexOf(' '); if (spaceIdx >= 0) { return fullClassName.substring(0, spaceIdx); } return fullClassName; } /** * Sets the element's primary style name and updates all dependent style * names. * * @param elem * the element whose style is to be reset * @param style * the new primary style name * @see #setStyleName(Element, String, boolean) */ protected static void setStylePrimaryName(Element elem, String style) { if (elem == null) { throw new RuntimeException(NULL_HANDLE_MSG); } // Style names cannot contain leading or trailing whitespace, and cannot // legally be empty. style = style.trim(); if (style.length() == 0) { throw new IllegalArgumentException(EMPTY_STYLENAME_MSG); } updatePrimaryAndDependentStyleNames(elem, style); } private Element element; /** * Adds a dependent style name by specifying the style name's suffix. The * actual form of the style name that is added is: * * <pre class="code"> * getStylePrimaryName() + '-' + styleSuffix * </pre> * * @param styleSuffix * the suffix of the dependent style to be added. * @see #setStylePrimaryName(String) * @see #removeStyleDependentName(String) * @see #setStyleDependentName(String, boolean) * @see #addStyleName(String) */ public void addStyleDependentName(String styleSuffix) { setStyleDependentName(styleSuffix, true); } /** * Adds a secondary or dependent style name to this object. A secondary * style name is an additional style name that is, in HTML/CSS terms, * included as a space-separated token in the value of the CSS * <code>class</code> attribute for this object's root element. * * <p> * The most important use for this method is to add a special kind of * secondary style name called a <i>dependent style name</i>. To add a * dependent style name, use {@link #addStyleDependentName(String)}, which * will prefix the 'style' argument with the result of * {@link #getStylePrimaryName()} (followed by a '-'). For example, suppose * the primary style name is <code>gwt-TextBox</code>. If the following * method is called as <code>obj.setReadOnly(true)</code>: * </p> * * <pre class="code"> * public void setReadOnly(boolean readOnly) { * isReadOnlyMode = readOnly; * // Create a dependent style name. * String readOnlyStyle = "readonly"; * if (readOnly) { * addStyleDependentName(readOnlyStyle); * } else { * removeStyleDependentName(readOnlyStyle); * } * } * </pre> * * <p> * then both of the CSS style rules below will be applied: * </p> * * <pre class="code"> * * // This rule is based on the primary style name and is always active. * .gwt-TextBox { * font-size: 12pt; * } * * // This rule is based on a dependent style name that is only active * // when the widget has called addStyleName(getStylePrimaryName() + * // "-readonly"). * .gwt-TextBox-readonly { * background-color: lightgrey; * border: none; * } * </pre> * * <p> * The code can also be simplified with * {@link #setStyleDependentName(String, boolean)}: * </p> * * <pre class="code"> * public void setReadOnly(boolean readOnly) { * isReadOnlyMode = readOnly; * setStyleDependentName("readonly", readOnly); * } * </pre> * * <p> * Dependent style names are powerful because they are automatically updated * whenever the primary style name changes. Continuing with the example * above, if the primary style name changed due to the following call: * </p> * * <pre class="code"> * setStylePrimaryName("my-TextThingy"); * </pre> * * <p> * then the object would be re-associated with following style rules, * removing those that were shown above. * </p> * * <pre class="code"> * .my-TextThingy { * font-size: 20pt; * } * * .my-TextThingy-readonly { * background-color: red; * border: 2px solid yellow; * } * </pre> * * <p> * Secondary style names that are not dependent style names are not * automatically updated when the primary style name changes. * </p> * * @param style * the secondary style name to be added * @see UIObject * @see #removeStyleName(String) */ public void addStyleName(String style) { setStyleName(style, true); } /** * Ensure that the main {@link Element} for this {@link UIObject} has an ID * property set, which allows it to integrate with third-party libraries and * test tools. Complex {@link Widget}s will also set the IDs of their * important sub-elements. * * If the main element already has an ID, this method WILL override it. * * The ID that you specify will be prefixed by the static string * {@link #DEBUG_ID_PREFIX}. * * This method will be compiled out and will have no effect unless you * inherit the DebugID module in your gwt.xml file by adding the following * line: * * <pre class="code"> * <inherits name="com.google.gwt.user.Debug"/> * </pre> * * @param id * the ID to set on the main element */ public final void ensureDebugId(String id) { debugIdImpl.ensureDebugId(this, id); } /** * Gets the object's absolute left position in pixels, as measured from the * browser window's client area. * * @return the object's absolute left position */ public int getAbsoluteLeft() { return getElement().getAbsoluteLeft(); } /** * Gets the object's absolute top position in pixels, as measured from the * browser window's client area. * * @return the object's absolute top position */ public int getAbsoluteTop() { return getElement().getAbsoluteTop(); } /** * Gets a handle to the object's underlying DOM element. * * This method should not be overridden. It is non-final solely to support * legacy code that depends upon overriding it. If it is overridden, the * subclass implementation must not return a different element than was * previously set using {@link #setElement(Element)}. * * @return the object's browser element */ public Element getElement() { assert (element != null) : MISSING_ELEMENT_ERROR; return element; } /** * Gets the object's offset height in pixels. This is the total height of * the object, including decorations such as border and padding, but not * margin. * * @return the object's offset height */ public int getOffsetHeight() { return getElement().implAccess().ensureRemote().getPropertyInt("offsetHeight"); } /** * Gets the object's offset width in pixels. This is the total width of the * object, including decorations such as border and padding, but not margin. * * @return the object's offset width */ public int getOffsetWidth() { return getElement().implAccess().ensureRemote().getPropertyInt("offsetWidth"); } /** * Gets all of the object's style names, as a space-separated list. If you * wish to retrieve only the primary style name, call * {@link #getStylePrimaryName()}. * * @return the objects's space-separated style names * @see #getStylePrimaryName() */ public String getStyleName() { return getStyleName(getStyleElement()); } /** * Gets the primary style name associated with the object. * * @return the object's primary style name * @see #setStyleName(String) * @see #addStyleName(String) * @see #removeStyleName(String) */ public String getStylePrimaryName() { return getStylePrimaryName(getStyleElement()); } /** * Gets the title associated with this object. The title is the 'tool-tip' * displayed to users when they hover over the object. * * @return the object's title */ public String getTitle() { return getElement().getPropertyString("title"); } @Override public boolean isVisible() { return isVisible(getElement()); } /** * Removes a dependent style name by specifying the style name's suffix. * * @param styleSuffix * the suffix of the dependent style to be removed * @see #setStylePrimaryName(Element, String) * @see #addStyleDependentName(String) * @see #setStyleDependentName(String, boolean) */ public void removeStyleDependentName(String styleSuffix) { setStyleDependentName(styleSuffix, false); } /** * Removes a style name. This method is typically used to remove secondary * style names, but it can be used to remove primary stylenames as well. * That use is not recommended. * * @param style * the secondary style name to be removed * @see #addStyleName(String) * @see #setStyleName(String, boolean) */ public void removeStyleName(String style) { setStyleName(style, false); } /** * Sets the object's height. This height does not include decorations such * as border, margin, and padding. * * @param height * the object's new height, in CSS units (e.g. "10px", "1em") */ public void setHeight(String height) { // This exists to deal with an inconsistency in IE's implementation // where // it won't accept negative numbers in length measurements assert extractLengthValue( height.trim().toLowerCase(Locale.ROOT)) >= 0 : "CSS heights should not be negative"; getElement().getStyle().setProperty("height", height); } /** * Sets the object's size, in pixels, not including decorations such as * border, margin, and padding. * * @param width * the object's new width, in pixels * @param height * the object's new height, in pixels */ public void setPixelSize(int width, int height) { if (width >= 0) { setWidth(width + "px"); } if (height >= 0) { setHeight(height + "px"); } } /** * Sets the object's size. This size does not include decorations such as * border, margin, and padding. * * @param width * the object's new width, in CSS units (e.g. "10px", "1em") * @param height * the object's new height, in CSS units (e.g. "10px", "1em") */ public void setSize(String width, String height) { setWidth(width); setHeight(height); } /** * Adds or removes a dependent style name by specifying the style name's * suffix. The actual form of the style name that is added is: * * <pre class="code"> * getStylePrimaryName() + '-' + styleSuffix * </pre> * * @param styleSuffix * the suffix of the dependent style to be added or removed * @param add * <code>true</code> to add the given style, <code>false</code> * to remove it * @see #setStylePrimaryName(Element, String) * @see #addStyleDependentName(String) * @see #setStyleName(String, boolean) * @see #removeStyleDependentName(String) */ public void setStyleDependentName(String styleSuffix, boolean add) { setStyleName(getStylePrimaryName() + '-' + styleSuffix, add); } /** * Clears all of the object's style names and sets it to the given style. * You should normally use {@link #setStylePrimaryName(String)} unless you * wish to explicitly remove all existing styles. * * @param style * the new style name * @see #setStylePrimaryName(String) */ public void setStyleName(String style) { setStyleName(getStyleElement(), style); } /** * Adds or removes a style name. This method is typically used to remove * secondary style names, but it can be used to remove primary stylenames as * well. That use is not recommended. * * @param style * the style name to be added or removed * @param add * <code>true</code> to add the given style, <code>false</code> * to remove it * @see #addStyleName(String) * @see #removeStyleName(String) */ public void setStyleName(String style, boolean add) { setStyleName(getStyleElement(), style, add); } /** * Sets the object's primary style name and updates all dependent style * names. * * @param style * the new primary style name * @see #addStyleName(String) * @see #removeStyleName(String) */ public void setStylePrimaryName(String style) { setStylePrimaryName(getStyleElement(), style); } /** * Sets the title associated with this object. The title is the 'tool-tip' * displayed to users when they hover over the object. * * @param title * the object's new title */ public void setTitle(String title) { if (title == null || title.length() == 0) { getElement().removeAttribute("title"); } else { getElement().setAttribute("title", title); } } @Override public void setVisible(boolean visible) { setVisible(getElement(), visible); } /** * Sets the object's width. This width does not include decorations such as * border, margin, and padding. * * @param width * the object's new width, in CSS units (e.g. "10px", "1em") */ public void setWidth(String width) { // This exists to deal with an inconsistency in IE's implementation // where // it won't accept negative numbers in length measurements assert extractLengthValue(width.trim().toLowerCase(Locale.ROOT)) >= 0 : "CSS widths should not be negative"; getElement().getStyle().setProperty("width", width); } /** * Sinks a named event. Note that only {@link Widget widgets} may actually * receive events, but can receive events from all objects contained within * them. * * @param eventTypeName * name of the event to sink on this element * @see com.google.gwt.user.client.Event */ public void sinkBitlessEvent(String eventTypeName) { DOM.sinkBitlessEvent(getElement(), eventTypeName); } /** * Adds a set of events to be sunk by this object. Note that only * {@link Widget widgets} may actually receive events, but can receive * events from all objects contained within them. * * @param eventBitsToAdd * a bitfield representing the set of events to be added to this * element's event set * @see com.google.gwt.user.client.Event */ public void sinkEvents(int eventBitsToAdd) { DOM.sinkEvents(getElement(), eventBitsToAdd | DOM.getEventsSunk(getElement())); } /** * This method is overridden so that any object can be viewed in the * debugger as an HTML snippet. * * @return a string representation of the object */ @Override public String toString() { if (element == null) { return "(null handle)"; } return getElement().getString(); } /** * Removes a set of events from this object's event list. * * @param eventBitsToRemove * a bitfield representing the set of events to be removed from * this element's event set * @see #sinkEvents * @see com.google.gwt.user.client.Event */ public void unsinkEvents(int eventBitsToRemove) { DOM.sinkEvents(getElement(), DOM.getEventsSunk(getElement()) & (~eventBitsToRemove)); } /** * Intended to be used to pull the value out of a CSS length. If the value * is "auto" or "inherit", 0 will be returned. * * @param s * The CSS length string to extract * @return The leading numeric portion of <code>s</code>, or 0 if "auto" or * "inherit" are passed in. */ private native double extractLengthValue(String s) /*-{ if (s == "auto" || s == "inherit" || s == "") { return 0; } else { // numberRegex is similar to java.lang.Number.floatRegex, but divides // the string into a leading numeric portion followed by an arbitrary // portion. var numberRegex = @com.google.gwt.user.client.ui.UIObject::numberRegex; if (!numberRegex) { numberRegex = @com.google.gwt.user.client.ui.UIObject::numberRegex = /^(\s*[+-]?((\d+\.?\d*)|(\.\d+))([eE][+-]?\d+)?)(.*)$/; } // Extract the leading numeric portion of s s = s.replace(numberRegex, "$1"); return parseFloat(s); } }-*/; private native void replaceNode(Element node, Element newNode) /*-{ var p = node.parentNode; if (!p) { return; } p.insertBefore(newNode, node); p.removeChild(node); }-*/; /** * Template method that returns the element to which style names will be * applied. By default it returns the root element, but this method may be * overridden to apply styles to a child element. * * @return the element to which style names will be applied */ protected Element getStyleElement() { return getElement(); } /** * Called when the user sets the id using the {@link #ensureDebugId(String)} * method. Subclasses of {@link UIObject} can override this method to add * IDs to their sub elements. If a subclass does override this method, it * should list the IDs (relative to the base ID), that will be applied to * each sub {@link Element} with a short description. For example: * <ul> * <li>-mysubelement = Applies to my sub element.</li> * </ul> * * Subclasses should make a super call to this method to ensure that the ID * of the main element is set. * * This method will not be called unless you inherit the DebugID module in * your gwt.xml file by adding the following line: * * <pre class="code"> * <inherits name="com.google.gwt.user.Debug"/> * </pre> * * @param baseID * the base ID used by the main element */ 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#claimElement(Element)}. * * @see PotentialElement * @see IsRenderable */ protected Element resolvePotentialElement() { throw new UnsupportedOperationException(); } /** * Sets this object's browser element. UIObject subclasses must call this * method before attempting to call any other methods, and it may only be * called once. * * @param elem * the object's element */ protected final void setElement(Element elem) { assert (element == null || PotentialElement.isPotential(element)) : SETELEMENT_TWICE_ERROR; this.element = elem; elem.uiObject = this; } /** * Replaces this object's browser element. * * This method exists only to support a specific use-case in Image, and * should not be used by other classes. * * @param elem * the object's new element */ void replaceElement(Element elem) { if (element != null) { // replace this.element in its parent with elem. replaceNode(element, elem); } this.element = elem; } /** * The implementation of the set debug id method, which does nothing by * default. */ public static class DebugIdImpl { // parameters public void ensureDebugId(Element elem, String baseID, String id) { } // parameters public void ensureDebugId(UIObject uiObject, String id) { } } /** * The implementation of the setDebugId method, which sets the id of the * {@link Element}s in this {@link UIObject}. */ public static class DebugIdImplEnabled extends DebugIdImpl { @Override public void ensureDebugId(Element elem, String baseID, String id) { assert baseID != null; baseID = (baseID.length() > 0) ? baseID + "-" : ""; String prefix = DebugInfo.getDebugIdPrefix(); String debugId = ((prefix == null) ? "" : prefix) + baseID + id; String attribute = DebugInfo.getDebugIdAttribute(); if (DebugInfo.isDebugIdAsProperty()) { elem.setPropertyString(attribute, debugId); } else { elem.setAttribute(attribute, debugId); } } @Override public void ensureDebugId(UIObject uiObject, String id) { uiObject.onEnsureDebugId(id); } } }