com.google.gwt.user.client.ui.HTMLPanel.java Source code

Java tutorial

Introduction

Here is the source code for com.google.gwt.user.client.ui.HTMLPanel.java

Source

/*
 * 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.Iterator;
import java.util.NoSuchElementException;

import com.google.gwt.dom.client.DivElement;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.safehtml.shared.SafeHtml;
import com.google.gwt.safehtml.shared.annotations.IsSafeHtml;
import com.google.gwt.safehtml.shared.annotations.SuppressIsSafeHtmlCastCheck;
import com.google.gwt.user.client.DOM;

/**
 * A panel that contains HTML, and which can attach child widgets to identified
 * elements within that HTML.
 */
public class HTMLPanel extends ComplexPanel {
    private static Element hiddenDiv;

    /**
     * A helper method for creating unique IDs for elements within dynamically-
     * generated HTML. This is important because no two elements in a document
     * should have the same id.
     *
     * @return a new unique identifier
     */
    public static String createUniqueId() {
        return Document.get().createUniqueId();
    }

    /**
     * Creates an HTML panel that wraps an existing element.
     *
     * This element must already be attached to the document. If the element is
     * removed from the document, you must call
     * {@link RootPanel#detachNow(Widget)}.
     *
     * @param element
     *            the element to be wrapped
     */
    public static HTMLPanel wrap(Element element) {
        // Assert that the element is attached.
        assert Document.get().getBody().isOrHasChild(element);
        HTMLPanel html = new HTMLPanel(element);
        // Mark it attached and remember it for cleanup.
        html.onAttach();
        RootPanel.detachOnWindowClose(html);
        return html;
    }

    /**
     * Initializes the panel's HTML from a given {@link SafeHtml} object.
     *
     * Similar to {@link #HTMLPanel(String)}
     *
     * @param safeHtml
     *            the html to set.
     */
    public HTMLPanel(SafeHtml safeHtml) {
        this(safeHtml.asString());
    }

    /**
     * Creates an HTML panel with the specified HTML contents inside a DIV
     * element. Any element within this HTML that has a specified id can contain
     * a child widget.
     *
     * @param html
     *            the panel's HTML
     */
    public HTMLPanel(@IsSafeHtml String html) {
        /*
         * Normally would call this("div", html), but that method has some
         * slightly expensive IE defensiveness that we just don't need for a div
         */
        setElement(Document.get().createDivElement());
        getElement().setInnerHTML(html);
    }

    /**
     * Creates an HTML panel whose root element has the given tag, and with the
     * specified HTML contents. Any element within this HTML that has a
     * specified id can contain a child widget.
     *
     * @param tag
     *            the tag of the root element
     * @param html
     *            the panel's HTML
     */
    @SuppressIsSafeHtmlCastCheck
    public HTMLPanel(String tag, @IsSafeHtml String html) {
        // Optimization for when the HTML is empty.
        if ("".equals(html)) {
            setElement(Document.get().createElement(tag));
            return;
        }
        /*
         * IE has very arbitrary rules about what will and will not accept
         * innerHTML. <table> and <tbody> simply won't, the property is read
         * only. <p> will explode if you incorrectly try to put another <p>
         * inside of it. And who knows what else.
         *
         * However, if you cram a complete, possibly incorrect structure inside
         * a div, IE will swallow it gladly. So that's what we do here in the
         * name of IE robustification.
         */
        StringBuilder b = new StringBuilder();
        b.append('<').append(tag).append('>').append(html);
        b.append("</").append(tag).append('>');
        // We could use the static hiddenDiv, but that thing is attached
        // to the document. The caller might not want that.
        DivElement scratchDiv = Document.get().createDivElement();
        scratchDiv.setInnerHTML(b.toString());
        setElement(scratchDiv.getFirstChildElement());
        getElement().removeFromParent();
    }

    /**
     * Construct a new {@link HTMLPanel} with the specified element.
     *
     * @param elem
     *            the element at the root of the panel
     */
    private HTMLPanel(Element elem) {
        setElement(elem);
    }

    /**
     * Adds a child widget to the panel.
     *
     * @param widget
     *            the widget to be added
     */
    @Override
    public void add(Widget widget) {
        add(widget, getElement());
    }

    /**
     * Adds a child widget to the panel, contained within an HTML element. It is
     * up to the caller to ensure that the given element is a child of this
     * panel's root element.
     *
     * @param widget
     *            the widget to be added
     * @param elem
     *            the element within which it will be contained
     */
    @Override
    public void add(Widget widget, Element elem) {
        super.add(widget, elem);
    }

    /**
     * Adds a child widget to the panel, contained within the HTML element
     * specified by a given id.
     *
     * @param widget
     *            the widget to be added
     * @param id
     *            the id of the element within which it will be contained
     */
    public void add(Widget widget, String id) {
        final Element elem = getElementById(id);
        if (elem == null) {
            throw new NoSuchElementException(id);
        }
        add(widget, elem);
    }

    /**
     * Overloaded version for IsWidget.
     *
     * @see #addAndReplaceElement(Widget,Element)
     *
     * @deprecated use {@link #addAndReplaceElement(IsWidget, Element)}
     */
    @Deprecated
    public void addAndReplaceElement(IsWidget widget, com.google.gwt.user.client.Element toReplace) {
        this.addAndReplaceElement(widget.asWidget(), toReplace);
    }

    /**
     * Overloaded version for IsWidget.
     *
     * @see #addAndReplaceElement(Widget,Element)
     */
    public void addAndReplaceElement(IsWidget widget, Element toReplace) {
        this.addAndReplaceElement(widget.asWidget(), toReplace);
    }

    /**
     * Overloaded version for IsWidget.
     *
     * @see #addAndReplaceElement(Widget,String)
     */
    public void addAndReplaceElement(IsWidget widget, String id) {
        this.addAndReplaceElement(widget.asWidget(), id);
    }

    /**
     * Adds a child widget to the panel, replacing the HTML element.
     *
     * @param widget
     *            the widget to be added
     * @param toReplace
     *            the element to be replaced by the widget
     * @deprecated use {@link #addAndReplaceElement(Widget, Element)}
     */
    @Deprecated
    public void addAndReplaceElement(Widget widget, com.google.gwt.user.client.Element toReplace) {
        /*
         * Early exit if the element to replace and the replacement are the
         * same. If we remove the new widget, we would also remove the element
         * to replace.
         */
        if (toReplace == widget.getElement()) {
            return;
        }
        // Logic pulled from super.add(), replacing the element rather than
        // adding.
        // Detach new child. Okay if its a child of the element to replace.
        widget.removeFromParent();
        // Logical detach of all children of the element to replace.
        Widget toRemove = null;
        Iterator<Widget> children = getChildren().iterator();
        while (children.hasNext()) {
            Widget next = children.next();
            if (toReplace.isOrHasChild(next.getElement())) {
                if (next.getElement() == toReplace) {
                    /*
                     * If the element that we are replacing is itself a widget,
                     * then we cannot remove it until the new widget has been
                     * inserted, or we lose the location of the element to
                     * replace. Save the widget to remove for now, and remove it
                     * after inserting the new widget.
                     */
                    toRemove = next;
                    break;
                }
                children.remove();
            }
        }
        // Logical attach.
        getChildren().add(widget);
        // Physical attach.
        if (toRemove == null) {
            toReplace.getParentNode().replaceChild(widget.getElement(), toReplace);
        } else {
            /*
             * The element being replaced is a widget, which needs to be
             * removed. First insert the new widget at the same location, then
             * remove the old widget.
             */
            toReplace.getParentNode().insertBefore(widget.getElement(), toReplace);
            remove(toRemove);
        }
        // Adopt.
        adopt(widget);
    }

    /**
     * Adds a child widget to the panel, replacing the HTML element.
     *
     * @param widget
     *            the widget to be added
     * @param toReplace
     *            the element to be replaced by the widget
     */
    public final void addAndReplaceElement(Widget widget, Element toReplace) {
        addAndReplaceElement(widget, DOM.asOld(toReplace));
    }

    /**
     * Adds a child widget to the panel, replacing the HTML element specified by
     * a given id.
     *
     * @param widget
     *            the widget to be added
     * @param id
     *            the id of the element to be replaced by the widget
     */
    public void addAndReplaceElement(Widget widget, String id) {
        final Element toReplace = getElementById(id);
        if (toReplace == null) {
            throw new NoSuchElementException(id);
        }
        addAndReplaceElement(widget, toReplace);
    }

    /**
     * Finds an {@link Element element} within this panel by its id.
     *
     * This method uses
     * {@link com.google.gwt.dom.client.Document#getElementById(String)}, so the
     * id must still be unique within the document.
     *
     * @param id
     *            the id of the element to be found
     * @return the element with the given id, or <code>null</code> if none is
     *         found
     */
    public Element getElementById(String id) {
        Element elem = isAttached() ? Document.get().getElementById(id) : attachToDomAndGetElement(id);
        return DOM.asOld(elem);
    }

    /**
     * Performs a {@link Document#getElementById(String)} after attaching the
     * panel's element into a hidden DIV in the document's body. Attachment is
     * necessary to be able to use the native getElementById. The panel's
     * element will be re-attached to its original parent (if any) after the
     * method returns.
     *
     * @param id
     *            the id whose associated element is to be retrieved
     * @return the associated element, or <code>null</code> if none is found
     */
    private Element attachToDomAndGetElement(String id) {
        // If the hidden DIV has not been created, create it.
        if (hiddenDiv == null) {
            hiddenDiv = Document.get().createDivElement();
            UIObject.setVisible(hiddenDiv, false);
            RootPanel.getBodyElement().appendChild(hiddenDiv);
        }
        // Hang on to the panel's original parent and sibling elements so that
        // it
        // can be replaced.
        Element origParent = getElement().getParentElement();
        Element origSibling = getElement().getNextSiblingElement();
        // Attach the panel's element to the hidden div.
        hiddenDiv.appendChild(getElement());
        // Now that we're attached to the DOM, we can use getElementById.
        Element child = Document.get().getElementById(id);
        // Put the panel's element back where it was.
        if (origParent != null) {
            origParent.insertBefore(getElement(), origSibling);
        } else {
            hiddenDiv.removeChild(getElement());
        }
        return child;
    }
}