blob: 1f4da9fcc4b9131675f1723db5e746653b3f7246 [file] [log] [blame]
/*
* Copyright 2007 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.GWT;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
/**
* An absolute panel positions all of its children absolutely, allowing them to
* overlap.
*
* <p>
* Note that this panel will not automatically resize itself to allow enough
* room for its absolutely-positioned children. It must be explicitly sized in
* order to make room for them.
* </p>
*
* <p>
* Once a widget has been added to an absolute panel, the panel effectively
* "owns" the positioning of the widget. Any existing positioning attributes on
* the widget may be modified by the panel.
* </p>
*
* <h3>Use in UiBinder Templates</h3>
* <p>
* AbsolutePanel elements in {@link com.google.gwt.uibinder.client.UiBinder
* UiBinder} templates lay out their children with absolute position, using
* &lt;g:at> elements. Each at element should have <code>left</code> and
* <code>top</code> attributes in pixels. They also can contain widget children
* directly, with no position specified.
*
* <p>
* For example:
*
* <pre>
* &lt;g:AbsolutePanel>
* &lt;g:at left='10' top='20'>
* &lt;g:Label>Lorem ipsum...&lt;/g:Label>
* &lt;/g:at>
* &lt;g:Label>...dolores est.&lt;/g:Label>
* &lt;/g:AbsolutePanel>
* </pre>
*/
public class AbsolutePanel extends ComplexPanel implements
InsertPanel.ForIsWidget {
/**
* Changes a DOM element's positioning to static.
*
* @param elem the DOM element
*/
private static void changeToStaticPositioning(Element elem) {
DOM.setStyleAttribute(elem, "left", "");
DOM.setStyleAttribute(elem, "top", "");
DOM.setStyleAttribute(elem, "position", "");
}
/**
* Creates an empty absolute panel.
*/
public AbsolutePanel() {
this(DOM.createDiv());
// Setting the panel's position style to 'relative' causes it to be treated
// as a new positioning context for its children.
DOM.setStyleAttribute(getElement(), "position", "relative");
DOM.setStyleAttribute(getElement(), "overflow", "hidden");
}
/**
* Creates an AbsolutePanel with the given element. This is protected so that
* it can be used by {@link RootPanel} or a subclass that wants to substitute
* another element. The element is presumed to be a &lt;div&gt;.
*
* @param elem the element to be used for this panel
*/
protected AbsolutePanel(Element elem) {
setElement(elem);
}
@Override
public void add(Widget w) {
super.add(w, getElement());
}
/**
* Adds a widget to the panel at the specified position. Setting a position of
* <code>(-1, -1)</code> will cause the child widget to be positioned
* statically.
*
* @param w the widget to be added
* @param left the widget's left position
* @param top the widget's top position
*/
public void add(Widget w, int left, int top) {
// In order to avoid the potential for a flicker effect, it is necessary
// to set the position of the widget before adding it to the AbsolutePanel.
// The Widget should be removed from its parent before any positional
// changes are made to prevent flickering.
w.removeFromParent();
int beforeIndex = getWidgetCount();
setWidgetPositionImpl(w, left, top);
insert(w, beforeIndex);
verifyPositionNotStatic(w);
}
/**
* Gets the position of the left outer border edge of the widget relative to
* the left outer border edge of the panel.
*
* @param w the widget whose position is to be retrieved
* @return the widget's left position
*/
public int getWidgetLeft(Widget w) {
checkWidgetParent(w);
return DOM.getAbsoluteLeft(w.getElement())
- DOM.getAbsoluteLeft(getElement());
}
/**
* Gets the position of the top outer border edge of the widget relative to
* the top outer border edge of the panel.
*
* @param w the widget whose position is to be retrieved
* @return the widget's top position
*/
public int getWidgetTop(Widget w) {
checkWidgetParent(w);
return DOM.getAbsoluteTop(w.getElement())
- DOM.getAbsoluteTop(getElement());
}
public void insert(Widget w, int beforeIndex) {
insert(w, getElement(), beforeIndex, true);
}
/**
* Convenience overload to allow {@link IsWidget} to be used directly.
*/
public void insert(IsWidget w, int beforeIndex) {
insert(asWidgetOrNull(w), beforeIndex);
}
/**
* Inserts a child widget at the specified position before the specified
* index. Setting a position of <code>(-1, -1)</code> will cause the child
* widget to be positioned statically. If the widget is already a child of
* this panel, it will be moved to the specified index.
*
* @param w the child widget to be inserted
* @param left the widget's left position
* @param top the widget's top position
* @param beforeIndex the index before which it will be inserted
* @throws IndexOutOfBoundsException if <code>beforeIndex</code> is out of
* range
*/
public void insert(Widget w, int left, int top, int beforeIndex) {
// In order to avoid the potential for a flicker effect, it is necessary
// to set the position of the widget before adding it to the AbsolutePanel.
// The Widget should be removed from its parent before any positional
// changes are made to prevent flickering.
w.removeFromParent();
setWidgetPositionImpl(w, left, top);
insert(w, beforeIndex);
verifyPositionNotStatic(w);
}
/**
* Overrides {@link ComplexPanel#remove(Widget)} to change the removed
* Widget's element back to static positioning.This is done so that any
* positioning changes to the widget that were done by the panel are undone
* when the widget is disowned from the panel.
*/
@Override
public boolean remove(Widget w) {
boolean removed = super.remove(w);
if (removed) {
changeToStaticPositioning(w.getElement());
}
return removed;
}
/**
* Sets the position of the specified child widget. Setting a position of
* <code>(-1, -1)</code> will cause the child widget to be positioned
* statically.
*
* @param w the child widget to be positioned
* @param left the widget's left position
* @param top the widget's top position
*/
public void setWidgetPosition(Widget w, int left, int top) {
checkWidgetParent(w);
setWidgetPositionImpl(w, left, top);
verifyPositionNotStatic(w);
}
protected void setWidgetPositionImpl(Widget w, int left, int top) {
Element h = w.getElement();
if (left == -1 && top == -1) {
changeToStaticPositioning(h);
} else {
DOM.setStyleAttribute(h, "position", "absolute");
DOM.setStyleAttribute(h, "left", left + "px");
DOM.setStyleAttribute(h, "top", top + "px");
}
}
private void checkWidgetParent(Widget w) {
if (w.getParent() != this) {
throw new IllegalArgumentException(
"Widget must be a child of this panel.");
}
}
/**
* Verify that the given widget is not statically positioned on the page, i.e.
* relative to the document window, unless the widget is in fact directly
* attached to the document BODY. Note that the current use of this method is
* not comprehensive, since we can only verify the offsetParent if both parent
* (AbsolutePanel) and child widget are both visible and attached to the DOM
* when this test is executed.
*
* @param child the widget whose position and placement should be tested
*/
private void verifyPositionNotStatic(Widget child) {
// Only verify widget position in Development Mode
if (GWT.isProdMode()) {
return;
}
// Non-visible or detached elements have no offsetParent
if (child.getElement().getOffsetParent() == null) {
return;
}
// Check if offsetParent == parent
if (child.getElement().getOffsetParent() == getElement()) {
return;
}
/*
* When this AbsolutePanel is the document BODY, e.g. RootPanel.get(), then
* no explicit position:relative is needed as children are already
* positioned relative to their parent. For simplicity we test against
* parent, not offsetParent, since in IE6+IE7 (but not IE8+) standards mode,
* the offsetParent, for elements whose parent is the document BODY, is the
* HTML element, not the BODY element.
*/
if ("body".equals(getElement().getNodeName().toLowerCase())) {
return;
}
/*
* Warn the developer, but allow the execution to continue in case legacy
* apps depend on broken CSS.
*/
String className = getClass().getName();
GWT.log("Warning: " + className + " descendants will be incorrectly "
+ "positioned, i.e. not relative to their parent element, when "
+ "'position:static', which is the CSS default, is in effect. One "
+ "possible fix is to call "
+ "'panel.getElement().getStyle().setPosition(Position.RELATIVE)'.",
// Stack trace provides context for the developer
new IllegalStateException(className
+ " is missing CSS 'position:{relative,absolute,fixed}'"));
}
}