@qooxdoo/framework
Version:
The JS Framework for Coders
591 lines (510 loc) • 15.9 kB
JavaScript
/* ************************************************************************
qooxdoo - the new era of web development
http://qooxdoo.org
Copyright:
2004-2011 1&1 Internet AG, Germany, http://www.1und1.de
License:
MIT: https://opensource.org/licenses/MIT
See the LICENSE file in the project's top-level directory for details.
Authors:
* Gabriel Munteanu (gabios)
* Christopher Zuendorf (czuendorf)
************************************************************************ */
/**
* The popup represents a widget that gets shown above other widgets,
* usually to present more info/details regarding an item in the application.
*
* There are 3 usages for now:
*
* <pre class='javascript'>
* var widget = new qx.ui.mobile.form.Button("Error!");
* var popup = new qx.ui.mobile.dialog.Popup(widget);
* popup.show();
* </pre>
* Here we show a popup consisting of a single buttons alerting the user
* that an error has occurred.
* It will be centered to the screen.
* <pre class='javascript'>
* var label = new qx.ui.mobile.basic.Label("Item1");
* var widget = new qx.ui.mobile.form.Button("Error!");
* var popup = new qx.ui.mobile.dialog.Popup(widget, label);
* popup.show();
* widget.addListener("tap", function(){
* popup.hide();
* });
*
* </pre>
*
* In this case everything is as above, except that the popup will get shown next to "label"
* so that the user can understand that the info presented is about the "Item1"
* we also add a tap listener to the button that will hide out popup.
*
* Once created, the instance is reused between show/hide calls.
*
* <pre class='javascript'>
* var widget = new qx.ui.mobile.form.Button("Error!");
* var popup = new qx.ui.mobile.dialog.Popup(widget);
* popup.placeTo(25,100);
* popup.show();
* </pre>
*
* Same as the first example, but this time the popup will be shown at the 25,100 coordinates.
*
*
*/
qx.Class.define("qx.ui.mobile.dialog.Popup", {
extend: qx.ui.mobile.core.Widget,
statics: {
ROOT: null
},
/**
* @param widget {qx.ui.mobile.core.Widget} the widget that will be shown in the popup
* @param anchor {qx.ui.mobile.core.Widget?} optional parameter, a widget to attach this popup to
*/
construct(widget, anchor) {
super();
this.exclude();
if (qx.ui.mobile.dialog.Popup.ROOT == null) {
qx.ui.mobile.dialog.Popup.ROOT = qx.core.Init.getApplication().getRoot();
}
qx.ui.mobile.dialog.Popup.ROOT.add(this);
this.__anchor = anchor;
if (widget) {
this._initializeChild(widget);
}
},
properties: {
// overridden
defaultCssClass: {
refine: true,
init: "popup"
},
/**
* The label/caption/text of the qx.ui.mobile.basic.Atom instance
*/
title: {
apply: "_applyTitle",
nullable: true,
check: "String",
event: "changeTitle"
},
/**
* Any URI String supported by qx.ui.mobile.basic.Image to display an icon
*/
icon: {
check: "String",
apply: "_applyIcon",
nullable: true,
event: "changeIcon"
},
/**
* Whether the popup should be displayed modal.
*/
modal: {
init: false,
check: "Boolean",
nullable: false
},
/**
* Indicates whether the a modal popup should disappear when user taps/clicks on Blocker.
*/
hideOnBlockerTap: {
check: "Boolean",
init: false
}
},
members: {
__isShown: false,
__isPlaced: false,
__childrenContainer: null,
__percentageTop: null,
__anchor: null,
__widget: null,
__titleWidget: null,
__lastPopupDimension: null,
/**
* Event handler. Called whenever the position of the popup should be updated.
*/
_updatePosition() {
// Traverse single anchor classes for removal, for preventing 'domupdated' event if no CSS classes changed.
var anchorClasses = ["top", "bottom", "left", "right", "anchor"];
for (var i = 0; i < anchorClasses.length; i++) {
this.removeCssClass(anchorClasses[i]);
}
if (this.__anchor) {
this.addCssClass("anchor");
var rootHeight = qx.ui.mobile.dialog.Popup.ROOT.getHeight();
var rootWidth = qx.ui.mobile.dialog.Popup.ROOT.getWidth();
var rootPosition = qx.bom.element.Location.get(
qx.ui.mobile.dialog.Popup.ROOT.getContainerElement()
);
var anchorPosition = qx.bom.element.Location.get(
this.__anchor.getContainerElement()
);
var popupDimension = qx.bom.element.Dimension.getSize(
this.getContainerElement()
);
this.__lastPopupDimension = popupDimension;
var computedPopupPosition = qx.util.placement.Placement.compute(
popupDimension,
{
width: rootPosition.left + rootWidth,
height: rootPosition.top + rootHeight
},
anchorPosition,
{
left: 0,
right: 0,
top: 0,
bottom: 0
},
"bottom-left",
"keep-align",
"keep-align"
);
// Reset Anchor.
this._resetPosition();
var isTop = anchorPosition.top > computedPopupPosition.top;
var isLeft = anchorPosition.left > computedPopupPosition.left;
computedPopupPosition.top =
computedPopupPosition.top - rootPosition.top;
computedPopupPosition.left =
computedPopupPosition.left - rootPosition.left;
var isOutsideViewPort =
computedPopupPosition.top < 0 ||
computedPopupPosition.left < 0 ||
computedPopupPosition.left + popupDimension.width > rootWidth ||
computedPopupPosition.top + popupDimension.height > rootHeight;
if (isOutsideViewPort) {
this._positionToCenter();
} else {
if (isTop) {
this.addCssClass("bottom");
} else {
this.addCssClass("top");
}
if (isLeft) {
this.addCssClass("right");
} else {
this.addCssClass("left");
}
this.placeTo(computedPopupPosition.left, computedPopupPosition.top);
}
} else if (this.__childrenContainer && !this.__isPlaced) {
// No Anchor and not placed to point manually
this._positionToCenter();
}
},
/**
* This method shows the popup.
* First it updates the position, then registers the event handlers, and shows it.
*/
show() {
if (!this.__isShown) {
qx.core.Init.getApplication().fireEvent("popup");
this.__registerEventListener();
// Needs to be added to screen, before rendering position, for calculating
// objects height.
super.show();
// Now render position.
this._updatePosition();
}
this.__isShown = true;
if (this.getModal() === true) {
qx.ui.mobile.core.Blocker.getInstance().show();
if (this.getHideOnBlockerTap()) {
qx.ui.mobile.core.Blocker.getInstance().addListener(
"tap",
this.hide,
this
);
}
}
},
/**
* Hides the popup.
*/
hide() {
if (this.__isShown) {
this.__unregisterEventListener();
this.exclude();
}
this.__isShown = false;
if (this.getModal()) {
qx.ui.mobile.core.Blocker.getInstance().hide();
}
qx.ui.mobile.core.Blocker.getInstance().removeListener(
"tap",
this.hide,
this
);
},
/**
* Hides the popup after a given time delay.
* @param delay {Integer} time delay in ms.
*/
hideWithDelay(delay) {
if (delay) {
qx.lang.Function.delay(this.hide, delay, this);
} else {
this.hide();
}
},
/**
* Returns the shown state of this popup.
* @return {Boolean} whether the popup is shown or not.
*/
isShown() {
return this.__isShown;
},
/**
* Toggles the visibility of this popup.
*/
toggleVisibility() {
if (this.__isShown == true) {
this.hide();
} else {
this.show();
}
},
/**
* This method positions the popup widget at the coordinates specified.
* @param left {Integer} - the value the will be set to container's left style property
* @param top {Integer} - the value the will be set to container's top style property
*/
placeTo(left, top) {
this.__isPlaced = true;
this._setStyle("left", left + "px");
this._setStyle("top", top + "px");
},
/**
* Tracks the user tap on root and hides the widget if <code>pointerdown</code> event
* occurs outside of the widgets bounds.
* @param evt {qx.event.type.Pointer} the pointer event.
*/
_trackUserTap(evt) {
var clientX = evt.getViewportLeft();
var clientY = evt.getViewportTop();
var popupLocation = qx.bom.element.Location.get(
this.getContainerElement()
);
var isOutsideWidget =
clientX < popupLocation.left ||
clientX > popupLocation.left + this.__lastPopupDimension.width ||
clientY > popupLocation.top + this.__lastPopupDimension.height ||
clientY < popupLocation.top;
if (isOutsideWidget) {
this.hide();
}
},
/**
* Centers this widget to window's center position.
*/
_positionToCenter() {
var container = this.getContainerElement();
container.style.position = "absolute";
container.style.marginLeft = -parseInt(container.offsetWidth / 2) + "px";
container.style.marginTop = -parseInt(container.offsetHeight / 2) + "px";
container.style.left = "50%";
container.style.top = "50%";
},
/**
* Resets the position of this element (left, top, margins...)
*/
_resetPosition() {
var container = this.getContainerElement();
container.style.left = "0px";
container.style.top = "0px";
container.style.marginLeft = null;
container.style.marginTop = null;
},
/**
* Registers all needed event listeners
*/
__registerEventListener() {
qx.core.Init.getApplication().addListener("stop", this.hide, this);
qx.core.Init.getApplication().addListener("popup", this.hide, this);
qx.event.Registration.addListener(
window,
"resize",
this._updatePosition,
this
);
if (this.__anchor) {
this.__anchor.addCssClass("anchor-target");
qx.ui.mobile.dialog.Popup.ROOT.addListener(
"pointerdown",
this._trackUserTap,
this
);
}
},
/**
* Unregisters all needed event listeners
*/
__unregisterEventListener() {
qx.core.Init.getApplication().removeListener("stop", this.hide, this);
qx.core.Init.getApplication().removeListener("popup", this.hide, this);
qx.event.Registration.removeListener(
window,
"resize",
this._updatePosition,
this
);
if (this.__anchor) {
this.__anchor.removeCssClass("anchor-target");
qx.ui.mobile.dialog.Popup.ROOT.removeListener(
"pointerdown",
this._trackUserTap,
this
);
}
},
/**
* This method creates the container where the popup's widget will be placed
* and adds it to the popup.
* @param widget {qx.ui.mobile.core.Widget} - what to show in the popup
*
*/
_initializeChild(widget) {
if (this.__childrenContainer == null) {
this.__childrenContainer = new qx.ui.mobile.container.Composite(
new qx.ui.mobile.layout.VBox()
);
this.__childrenContainer.setDefaultCssClass("popup-content");
this._add(this.__childrenContainer);
}
if (this._createTitleWidget()) {
this.__childrenContainer.remove(this._createTitleWidget());
this.__childrenContainer.add(this._createTitleWidget());
}
this.__childrenContainer.add(widget, {
flex: 1
});
widget.addListener("domupdated", this._updatePosition, this);
this.__widget = widget;
},
/**
* Creates the title atom widget.
*
* @return {qx.ui.mobile.basic.Atom} The title atom widget.
*/
_createTitleWidget() {
if (this.__titleWidget) {
return this.__titleWidget;
}
if (this.getTitle() || this.getIcon()) {
this.__titleWidget = new qx.ui.mobile.basic.Atom(
this.getTitle(),
this.getIcon()
);
this.__titleWidget.addCssClass("popup-title");
return this.__titleWidget;
} else {
return null;
}
},
// property apply
_applyTitle(value, old) {
if (value) {
if (this.__titleWidget) {
this.__titleWidget.setLabel(value);
} else {
this.__titleWidget = new qx.ui.mobile.basic.Atom(
value,
this.getIcon()
);
this.__titleWidget.addCssClass("popup-title");
if (this.__widget) {
this.__childrenContainer.addBefore(
this._createTitleWidget(),
this.__widget
);
} else {
if (this.__childrenContainer) {
this.__childrenContainer.add(this._createTitleWidget());
}
}
}
}
},
// property apply
_applyIcon(value, old) {
if (value) {
if (this.__titleWidget) {
this.__titleWidget.setIcon(value);
} else {
this.__titleWidget = new qx.ui.mobile.basic.Atom(
this.getTitle(),
value
);
this.__titleWidget.addCssClass("popup-title");
if (this.__widget) {
this.__childrenContainer.addBefore(
this._createTitleWidget(),
this.__widget
);
} else {
if (this.__childrenContainer) {
this.__childrenContainer.add(this._createTitleWidget());
}
}
}
}
},
/**
* Adds the widget that will be shown in this popup. This method can be used in the case when you have removed the widget from the popup
* or you haven't passed it in the constructor.
* @param widget {qx.ui.mobile.core.Widget} - what to show in the popup
*/
add(widget) {
this.removeWidget();
this._initializeChild(widget);
},
/**
* A widget to attach this popup to.
*
* @param widget {qx.ui.mobile.core.Widget} The anchor widget.
*/
setAnchor(widget) {
this.__anchor = widget;
this._updatePosition();
},
/**
* Returns the title widget.
*
* @return {qx.ui.mobile.basic.Atom} The title widget.
*/
getTitleWidget() {
return this.__titleWidget;
},
/**
* This method removes the widget shown in the popup.
* @return {qx.ui.mobile.core.Widget|null} The removed widget or <code>null</code>
* if the popup doesn't have an attached widget
*/
removeWidget() {
if (this.__widget) {
this.__widget.removeListener("domupdated", this._updatePosition, this);
this.__childrenContainer.remove(this.__widget);
return this.__widget;
} else {
if (qx.core.Environment.get("qx.debug")) {
qx.log.Logger.debug(this, "this popup has no widget attached yet");
}
return null;
}
}
},
destruct() {
this.__unregisterEventListener();
this._disposeObjects("__childrenContainer");
this.__isShown =
this.__isPlaced =
this.__percentageTop =
this._anchor =
this.__widget =
this.__lastPopupDimension =
null;
}
});