All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.swm.commons.mobile.client.widgets.popup.SimplePopup Maven / Gradle / Ivy

package de.swm.commons.mobile.client.widgets.popup;

import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.dom.client.*;
import com.google.gwt.dom.client.Style.Display;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.logical.shared.*;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.layout.client.Layout.Layer;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.Event.NativePreviewEvent;
import com.google.gwt.user.client.Event.NativePreviewHandler;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.RootLayoutPanel;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.UIObject;
import com.google.gwt.user.client.ui.Widget;
import de.swm.commons.mobile.client.SWMMobile;
import de.swm.commons.mobile.client.widgets.VerticalPanel;

public class SimplePopup extends SimplePanel implements HasCloseHandlers {

	public class Position {
		private int left = -1;
		private int top = -1;
		private double width = -1; // use given width
		private double height = -1; // use given height

		/**
		 * Specify position of the popup.
		 * 
		 * @param left x-coordinate of position
		 * @param top y-coordinate of position
		 */
		public Position(int left, int top) {
			this.left = left;
			this.top = top;
		}

		/**
		 * Specify position and dimension of the popup.
		 * 
		 * @param left x-coordinate of position
		 * @param top y-coordinate of position
		 * @param width width of popup in percentage (0.0 < width < 1.0) or -1 for calculated width
		 * @param height height of popup in percentage (0.0 < width < 1.0) or -1 for calculated height
		 */
		public Position(int left, int top, double width, double height) {
			this.left = left;
			this.top = top;
			this.width = width;
			this.height = height;
		}

		public int getLeft() {
			return left;
		}

		public int getTop() {
			return top;
		}
		
		public double getWidth() {
			return width;
		}
		
		public double getHeight() {
			return height;
		}
	}

	/**
	 * A callback that is used to set the position of a {@link SimplePopup} right before it is shown.
	 */
	public interface PositionCallback {

		/**
		 * Provides the opportunity to set the position of the SimplePopup right before the SimplePopup is shown. The
		 * offsetWidth and offsetHeight values of the SimplePopup are made available to allow for positioning based on
		 * its size.
		 * 
		 * @param offsetWidth
		 *            the offsetWidth of the SimplePopup
		 * @param offsetHeight
		 *            the offsetHeight of the SimplePopup
		 * @see SimplePopup#show(PositionCallback)
		 */
		Position getPosition(int offsetWidth, int offsetHeight);

	}

	protected final VerticalPanel panel;
	private boolean autoHide = false;
	private boolean displayed = false;
	private boolean glassEnabled = false;
	private boolean glassShowing = false;
	private Element glass;
	private HandlerRegistration nativePreviewHandlerRegistration;
    private HandlerRegistration resizeRegistration;
	/**
	 * Window resize handler used to keep the glass the proper size.
	 */
	private final ResizeHandler glassResizer = new ResizeHandler() {
		public void onResize(ResizeEvent event) {
			Style style = glass.getStyle();

			int winWidth = Window.getClientWidth();
			int winHeight = Window.getClientHeight();

			// Hide the glass while checking the document size. Otherwise it would
			// interfere with the measurement.
			style.setDisplay(Display.NONE);
			style.setWidth(0, Unit.PX);
			style.setHeight(0, Unit.PX);

			int width = Document.get().getScrollWidth();
			int height = Document.get().getScrollHeight();

			// Set the glass size to the larger of the window's client size or the
			// document's scroll size.
			style.setWidth(Math.max(width, winWidth), Unit.PX);
			style.setHeight(Math.max(height, winHeight), Unit.PX);

			// The size is set. Show the glass again.
			style.setDisplay(Display.BLOCK);
		}
	};

	public SimplePopup() {
		setStyleName(SWMMobile.getTheme().getMGWTCssBundle().getPopupsCss().simplePopup());
		panel = new VerticalPanel();
		setWidget(panel);
	}

	public SimplePopup(Widget[] widgets) {
		this();
		for (int i = 0; i < widgets.length; i++) {
			panel.add(widgets[i]);
		}
	}
	
	@Override
	public void add(Widget w) {
		panel.add(w);
	}

	@Override
	public void clear(){
		panel.clear();
	}


	public void setAutoHide(boolean autoHide) {
		this.autoHide = autoHide;
	}

	public void show(final PositionCallback cb) {
		if (displayed) {
			return;
		}
		
		setVisible(false);
		maybeShowGlass();

		nativePreviewHandlerRegistration = Event.addNativePreviewHandler(new NativePreviewHandler() {
			public void onPreviewNativeEvent(NativePreviewEvent event) {
				previewNativeEvent(event);
			}
		});
		
		RootLayoutPanel.get().add(this);

		// clear "overflow:hidden" property from parent layer to allow shadow to "bleed" over the edge
		Layer layer = (Layer) getLayoutData();
		layer.getContainerElement().getStyle().clearOverflow();
		layer.getContainerElement().getStyle().setZIndex(1);

		final SimplePopup instance = this;
		Scheduler.get().scheduleDeferred(new ScheduledCommand() {

			@Override
			public void execute() {
				// clear "bottom:0" and "right:0" properties from element => getOffsetWidth() and getOffsetHeight()
				// calculate correct values
				getElement().getStyle().clearBottom();
				getElement().getStyle().clearRight();

				Position pos = cb.getPosition(getOffsetWidth(), getOffsetHeight());
				double width = pos.getWidth() > 0 ? pos.getWidth() * 100.0 : getOffsetWidth();
				Unit widthUnit = pos.getWidth() > 0 ? Unit.PCT : Unit.PX;
				RootLayoutPanel.get().setWidgetLeftWidth(instance, pos.getLeft(), Unit.PX, width, widthUnit);
				
				double height = pos.getHeight() > 0 ? pos.getHeight() * 100.0 : getOffsetHeight();
				Unit heightUnit = pos.getHeight() > 0 ? Unit.PCT : Unit.PX;
				RootLayoutPanel.get().setWidgetTopHeight(instance, pos.getTop(), Unit.PX, height, heightUnit);
								
				onShow();
				
				displayed = true;
				setVisible(true);				
			}
		});
	}
	
	protected void onShow() {
		// template method for subclasses
	}

	protected void onHide() {
		// template method for subclasses
	}
	
	public void hide() {
		hide(false);
	}

	public void hide(boolean autoClosed) {
		if (displayed) {
			maybeShowGlass();
			RootLayoutPanel.get().remove(this);
			if (nativePreviewHandlerRegistration != null) {
				nativePreviewHandlerRegistration.removeHandler();
				nativePreviewHandlerRegistration = null;
			}
			displayed = false;
			onHide();
			CloseEvent.fire(this, this, autoClosed);
		}
	}

	public void showCentered(boolean glassEffect) {
		setGlassEnabled(glassEffect);
		show(new PositionCallback() {

			@Override
			public Position getPosition(int offsetWidth, int offsetHeight) {
				int left = (Window.getClientWidth() - offsetWidth) / 2;
				int top = (Window.getClientHeight() - offsetHeight) / 2;
				return new Position(left, top);
			}
		});
	}

	/**
	 * Normally, the popup is positioned directly below the relative target, with its left edge aligned with the left
	 * edge of the target. Depending on the width and height of the popup and the distance from the target to the bottom
	 * and right edges of the window, the popup may be displayed directly above the target, and/or its right edge may be
	 * aligned with the right edge of the target.
	 * 
	 * @param target
	 *            the target to show the popup below
	 */
	public void showRelativeTo(final UIObject target) {
		// Set the position of the popup right before it is shown.
		show(new PositionCallback() {

			@Override
			public Position getPosition(int offsetWidth, int offsetHeight) {
				return calculatePosition(target, offsetWidth, offsetHeight);
			}
		});
	}

	/**
	 * Positions the popup, called after the offset width and height of the popup are known.
	 * 
	 * @param relativeObject
	 *            the ui object to position relative to
	 * @param offsetWidth
	 *            the drop down's offset width
	 * @param offsetHeight
	 *            the drop down's offset height
	 */
	private Position calculatePosition(final UIObject relativeObject, int offsetWidth, int offsetHeight) {
		// Calculate left position for the popup. The computation for
		// the left position is bidi-sensitive.

		int textBoxOffsetWidth = relativeObject.getOffsetWidth();

		// Compute the difference between the popup's width and the
		// textbox's width
		int offsetWidthDiff = offsetWidth - textBoxOffsetWidth;

		// Left-align the popup.
		int left = relativeObject.getAbsoluteLeft();

		// If the suggestion popup is not as wide as the text box, always align to
		// the left edge of the text box. Otherwise, figure out whether to
		// left-align or right-align the popup.
		if (offsetWidthDiff > 0) {
			// Make sure scrolling is taken into account, since
			// box.getAbsoluteLeft() takes scrolling into account.
			int windowRight = Window.getClientWidth() + Window.getScrollLeft();
			int windowLeft = Window.getScrollLeft();

			// Distance from the left edge of the text box to the right edge
			// of the window
			int distanceToWindowRight = windowRight - left;

			// Distance from the left edge of the text box to the left edge of the
			// window
			int distanceFromWindowLeft = left - windowLeft;

			// If there is not enough space for the overflow of the popup's
			// width to the right of hte text box, and there IS enough space for the
			// overflow to the left of the text box, then right-align the popup.
			// However, if there is not enough space on either side, then stick with
			// left-alignment.
			if (distanceToWindowRight < offsetWidth && distanceFromWindowLeft >= offsetWidthDiff) {
				// Align with the right edge of the text box.
				left -= offsetWidthDiff;
			}

		}

		// Calculate top position for the popup

		int top = relativeObject.getAbsoluteTop();

		// Make sure scrolling is taken into account, since
		// box.getAbsoluteTop() takes scrolling into account.
		int windowTop = Window.getScrollTop();
		int windowBottom = Window.getScrollTop() + Window.getClientHeight();

		// Distance from the top edge of the window to the top edge of the
		// text box
		int distanceFromWindowTop = top - windowTop;

		// Distance from the bottom edge of the window to the bottom edge of
		// the text box
		int distanceToWindowBottom = windowBottom - (top + relativeObject.getOffsetHeight());

		// If there is not enough space for the popup's height below the text
		// box and there IS enough space for the popup's height above the text
		// box, then then position the popup above the text box. However, if there
		// is not enough space on either side, then stick with displaying the
		// popup below the text box.
		if (distanceToWindowBottom < offsetHeight && distanceFromWindowTop >= offsetHeight) {
			top -= offsetHeight;
		} else {
			// Position above the text box
			top += relativeObject.getOffsetHeight();
		}
		return new Position(left, top);
	}

	@Override
	public void setVisible(boolean visible) {
		// We use visibility here instead of UIObject's default of display
		// Because the panel is absolutely positioned, this will not create
		// "holes" in displayed contents and it allows normal layout passes
		// to occur so the size of the PopupPanel can be reliably determined.
		DOM.setStyleAttribute(getElement(), "visibility", visible ? "visible" : "hidden");
	}

	public HandlerRegistration addCloseHandler(CloseHandler handler) {
		return addHandler(handler, CloseEvent.getType());
	}

	/**
	 * When enabled, the background will be blocked with a semi-transparent pane the next time it is shown. If the
	 * PopupPanel is already visible, the glass will not be displayed until it is hidden and shown again.
	 * 
	 * @param enabled
	 *            true to enable, false to disable
	 */
	public void setGlassEnabled(boolean enabled) {
		glassEnabled = enabled;
		if (enabled && glass == null) {
			glass = Document.get().createDivElement();
			glass.setClassName(SWMMobile.getTheme().getMGWTCssBundle().getPopupsCss().simplePopupGlass());
			glass.getStyle().setPosition(com.google.gwt.dom.client.Style.Position.ABSOLUTE);
			glass.getStyle().setLeft(0, Unit.PX);
			glass.getStyle().setTop(0, Unit.PX);
		}
	}

    /**
     * Show or hide the glass.
     */
    private void maybeShowGlass() {
      if (!glassShowing) {
        if (glassEnabled) {
          Document.get().getBody().appendChild(glass);
          resizeRegistration = Window.addResizeHandler(glassResizer);
          glassResizer.onResize(null);
          glassShowing = true;
        }
      } else {
        Document.get().getBody().removeChild(glass);
        resizeRegistration.removeHandler();
        resizeRegistration = null;
        glassShowing = false;
      }
    }

	private boolean eventTargetsPopup(NativeEvent event) {
		EventTarget target = event.getEventTarget();
		if (Element.is(target)) {
			return getElement().isOrHasChild(Element.as(target));
		}
		return false;
	}

	private void previewNativeEvent(NativePreviewEvent event) {
		if (event.isCanceled() || event.isConsumed()) {
			return;
		}

		// If the event targets the popup or the partner, consume it
		Event nativeEvent = Event.as(event.getNativeEvent());
		boolean eventTargetsPopup = eventTargetsPopup(nativeEvent);
		if (eventTargetsPopup) {
			event.consume();
		}

		int type = nativeEvent.getTypeInt();
		switch (type) {
		case Event.ONMOUSEDOWN:
		case Event.ONTOUCHSTART:
			// Don't eat events if event capture is enabled, as this can
			// interfere with dialog dragging, for example.
			if (DOM.getCaptureElement() != null) {
				event.consume();
				return;
			}

			if (!eventTargetsPopup && autoHide) {
				hide(true);
				return;
			}
			break;
		case Event.ONMOUSEUP:
		case Event.ONMOUSEMOVE:
		case Event.ONCLICK:
		case Event.ONDBLCLICK:
		case Event.ONTOUCHEND:
			// Don't eat events if event capture is enabled, as this can
			// interfere with dialog dragging, for example.
			if (DOM.getCaptureElement() != null) {
				event.consume();
				return;
			}
			break;
		}
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy