@qooxdoo/framework
Version:
The JS Framework for Coders
583 lines (480 loc) • 15.6 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 : function(widget, anchor)
{
this.base(arguments);
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,
__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 : function()
{
// 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) {
// No Anchor
this._positionToCenter();
}
},
/**
* This method shows the popup.
* First it updates the position, then registers the event handlers, and shows it.
*/
show : function()
{
if (!this.__isShown)
{
qx.core.Init.getApplication().fireEvent("popup");
this.__registerEventListener();
// Move outside of viewport
this.placeTo(-1000,-1000);
// Needs to be added to screen, before rendering position, for calculating
// objects height.
this.base(arguments);
// 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 : function()
{
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 : function(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 : function() {
return this.__isShown;
},
/**
* Toggles the visibility of this popup.
*/
toggleVisibility : function() {
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 : function(left, top)
{
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 : function(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 : function()
{
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 : function()
{
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 : function()
{
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 : function()
{
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 : function(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 : function()
{
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 : function(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 : function(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 : function(widget)
{
this.removeWidget();
this._initializeChild(widget);
},
/**
* A widget to attach this popup to.
*
* @param widget {qx.ui.mobile.core.Widget} The anchor widget.
*/
setAnchor : function(widget) {
this.__anchor = widget;
this._updatePosition();
},
/**
* Returns the title widget.
*
* @return {qx.ui.mobile.basic.Atom} The title widget.
*/
getTitleWidget : function() {
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 : function()
{
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 : function()
{
this.__unregisterEventListener();
this._disposeObjects("__childrenContainer");
this.__isShown = this.__percentageTop = this._anchor = this.__widget = this.__lastPopupDimension = null;
}
});