/* * Ext GWT - Ext for GWT * Copyright(c) 2007-2009, Ext JS, LLC. * licensing@extjs.com * * http://extjs.com/license */ package com.extjs.gxt.ui.client.widget; import com.extjs.gxt.ui.client.core.CompositeElement; import com.extjs.gxt.ui.client.core.El; import com.extjs.gxt.ui.client.core.XDOM; import com.extjs.gxt.ui.client.event.BaseEvent; import com.extjs.gxt.ui.client.event.ComponentEvent; import com.extjs.gxt.ui.client.event.Events; import com.extjs.gxt.ui.client.event.FxEvent; import com.extjs.gxt.ui.client.event.Listener; import com.extjs.gxt.ui.client.event.PreviewEvent; import com.extjs.gxt.ui.client.fx.FxConfig; import com.extjs.gxt.ui.client.util.BaseEventPreview; import com.extjs.gxt.ui.client.util.Point; import com.extjs.gxt.ui.client.util.Rectangle; import com.google.gwt.event.dom.client.KeyCodes; import com.google.gwt.user.client.Element; import com.google.gwt.user.client.Event; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.ui.RootPanel; /** * A panel that can be displayed over other widgets. * * <dl> * <dt><b>Events:</b></dt> * * <dd><b>BeforeShow</b> : ComponentEvent(component)<br> * <div>Fires before the popup is displayed. Listeners can cancel the action by * calling {@link BaseEvent#setCancelled(boolean)}.</div> * <ul> * <li>component : this</li> * </ul> * </dd> * * <dd><b>Show</b> : ComponentEvent(component)<br> * <div>Fires after a popup is displayed.</div> * <ul> * <li>component : this</li> * </ul> * </dd> * * <dd><b>BeforeHide</b> : ComponentEvent(component)<br> * <div>Fires before the popup is hidden. Listeners can cancel the action by * calling {@link BaseEvent#setCancelled(boolean)}.</div> * <ul> * <li>component : this</li> * </ul> * </dd> * * <dd><b>Hide</b> : ComponentEvent(component)<br> * <div>Fires after a popup is hidden.</div> * <ul> * <li>component : this</li> * </ul> * </dd> * </dl> * * <dl> * <dt>Inherited Events:</dt> * <dd>LayoutContainer AfterLayout</dd> * <dd>ScrollContainer Scroll</dd> * <dd>Container BeforeAdd</dd> * <dd>Container Add</dd> * <dd>Container BeforeRemove</dd> * <dd>Container Remove</dd> * <dd>BoxComponent Move</dd> * <dd>BoxComponent Resize</dd> * <dd>Component Enable</dd> * <dd>Component Disable</dd> * <dd>Component BeforeHide</dd> * <dd>Component Hide</dd> * <dd>Component BeforeShow</dd> * <dd>Component Show</dd> * <dd>Component Attach</dd> * <dd>Component Detach</dd> * <dd>Component BeforeRender</dd> * <dd>Component Render</dd> * <dd>Component BrowserEvent</dd> * <dd>Component BeforeStateRestore</dd> * <dd>Component StateRestore</dd> * <dd>Component BeforeStateSave</dd> * <dd>Component SaveState</dd> * </dl> * * <dl> * <dt><b>CSS:</b></dt> * <dd>.x-popup (the popup itself)</dd> * </dl> */ public class Popup extends LayoutContainer { private int yOffset = 15; private int xOffset = 10; private boolean animate; private boolean autoFocus = true; private boolean autoHide = true; private boolean constrainViewport = true; private String defaultAlign = "tl-bl?"; private Element alignElem; private String alignPos; private int[] alignOffsets; private Point alignPoint; private BaseEventPreview preview = new BaseEventPreview() { @Override protected boolean onAutoHide(PreviewEvent ce) { if (Popup.this.onAutoHide(ce.getEvent())) { hide(); } return true; } }; /** * Creates a new popup panel. */ public Popup() { baseStyle = "x-popup"; shim = true; enableLayout = true; } /** * Centers the panel within the viewport. */ public void center() { if (rendered) { el().center(); } } /** * Returns the default alignment. * * @return the default align */ public String getDefaultAlign() { return defaultAlign; } /** * Any elements added to this list will be ignored when auto close is enabled. * * @return the list of ignored elements */ public CompositeElement getIgnoreList() { return preview.getIgnoreList(); } /** * Returns the x offset. * * @return the offset */ public int getXOffset() { return xOffset; } /** * Returns the y offsets. * * @return the offset */ public int getYOffset() { return yOffset; } /** * Hides the popup. */ public void hide() { if (!fireEvent(Events.BeforeHide, new ComponentEvent(this))) { return; } if (autoHide) { preview.remove(); } if (isAnimate()) { el().fadeOut(new FxConfig(new Listener<FxEvent>() { public void handleEvent(FxEvent fe) { afterHide(); } })); } else { afterHide(); } } /** * Returns true if animations are enabled. * * @return the animation state */ public boolean isAnimate() { return animate; } /** * Returns true if auto focus is enabled. * * @return the auto focus state */ public boolean isAutoFocus() { return autoFocus; } /** * Returns true if auto hide is enabled. * * @return the auto hide state */ public boolean isAutoHide() { return autoHide; } /** * Returns true if contrain to viewport is enabled. * * @return the constrain viewport state */ public boolean isConstrainViewport() { return constrainViewport; } /** * True to enable animations when showing and hiding (defaults to false). * * @param animate true to enable animations */ public void setAnimate(boolean animate) { this.animate = animate; } /** * True to move focus to the popup when being opened (defaults to true). * * @param autoFocus true for auto focus */ public void setAutoFocus(boolean autoFocus) { this.autoFocus = autoFocus; } /** * True to close the popup when the user clicks outside of the menu (default * to true). * * @param autoHide true for auto hide */ public void setAutoHide(boolean autoHide) { this.autoHide = autoHide; } /** * True to ensure popup is dislayed within the browser's viewport. * * @param constrainViewport true to constrain */ public void setConstrainViewport(boolean constrainViewport) { this.constrainViewport = constrainViewport; } /** * The default {@link El#alignTo} anchor position value for this menu relative * to its element of origin (defaults to "tl-bl?"). * * @param defaultAlign the default alignment */ public void setDefaultAlign(String defaultAlign) { this.defaultAlign = defaultAlign; } /** * Sets the xOffset when constrainViewport == true (defaults to 10). * * @param xOffset the x offset */ public void setXOffset(int xOffset) { this.xOffset = xOffset; } /** * Sets the yOffset when constrainViewport == true (defaults to 15). * * @param yOffset the offset */ public void setYOffset(int yOffset) { this.yOffset = yOffset; } /** * Displays the popup. */ public void show() { if (!fireEvent(Events.BeforeShow, new ComponentEvent(this))) return; Point p = new Point((int) Window.getClientWidth() / 2, (int) Window.getClientHeight() / 2); showAt(p.x, p.y); } /** * Displays the popup aligned to the bottom left of the widget. For exact * control of popup position see {@link #show(Element, String, int[])}. * * @param widget the widget to use for alignment */ public void show(Component widget) { if (!fireEvent(Events.BeforeShow, new ComponentEvent(this))) { return; } alignElem = widget.getElement(); onShowPopup(); } /** * Displays the popup. * * @param elem the element to align to * @param pos the position */ public void show(Element elem, String pos) { if (!fireEvent(Events.BeforeShow, new ComponentEvent(this))) return; alignElem = elem; alignPos = pos; onShowPopup(); } /** * Displays the popup. * * @param elem the element to align to * @param pos the postion * @param offsets the offsets */ public void show(Element elem, String pos, int[] offsets) { if (!fireEvent(Events.BeforeShow, new ComponentEvent(this))) { return; } alignElem = elem; alignPos = pos; alignOffsets = offsets; onShowPopup(); } /** * Shows the popup at the specified location. * * @param x the x coordinate * @param y the y coordinate */ public void showAt(int x, int y) { if (!fireEvent(Events.BeforeShow, new ComponentEvent(this))) { return; } alignPoint = new Point(x, y); onShowPopup(); } protected void afterHide() { RootPanel.get().remove(this); hidden = true; if (layer != null) { layer.hideShadow(); } el().setVisible(false); fireEvent(Events.Hide, new ComponentEvent(this)); } protected void afterShow() { el().setVisible(true); if (layer != null) { layer.sync(true); } if (isAutoFocus()) { focus(); } el().setZIndex(XDOM.getTopZIndex()); fireEvent(Events.Open, new ComponentEvent(this)); } protected void handleKeyUp(ComponentEvent ce) { int code = ce.getKeyCode(); ce.setComponent(this); onKeyPress(ce); switch (code) { case KeyCodes.KEY_ESCAPE: onAutoHide(ce.getEvent()); } } /** * Subclasses may override to cancel the hide from an auto hide. * * @param event the current event * @return true to close, false to cancel */ protected boolean onAutoHide(Event event) { return true; } @Override protected void onDetach() { super.onDetach(); if (preview != null) { preview.remove(); } } protected void onKeyPress(BaseEvent be) { } protected void onRender(Element target, int index) { super.onRender(target, index); el().makePositionable(true); preview.getIgnoreList().add(getElement()); } protected Popup onShowPopup() { RootPanel.get().add(this); hidden = false; Point p = null; if (alignElem != null) { alignPos = alignPos != null ? alignPos : getDefaultAlign(); alignOffsets = alignOffsets != null ? alignOffsets : new int[] {0, 2}; p = el().getAlignToXY(alignElem, alignPos, alignOffsets); } else if (alignPoint != null) { p = alignPoint; } el().setLeftTop(p.x, p.y); alignElem = null; alignPos = null; alignOffsets = null; alignPoint = null; el().makePositionable(true).setVisibility(false); if (constrainViewport) { int clientHeight = Window.getClientHeight() + XDOM.getBodyScrollTop(); int clientWidth = Window.getClientWidth() + XDOM.getBodyScrollLeft(); Rectangle r = el().getBounds(); int x = r.x; int y = r.y; if (y + r.height > clientHeight) { y = clientHeight - r.height - getYOffset(); el().setTop(y); } if (x + r.width > clientWidth) { x = clientWidth - r.width - getXOffset(); el().setLeft(x); } } el().setVisibility(true); if (autoHide) { preview.add(); } if (animate) { el().fadeIn(new FxConfig(new Listener<FxEvent>() { public void handleEvent(FxEvent fe) { afterShow(); } })); } else { afterShow(); } return this; } }