@qooxdoo/framework
Version:
The JS Framework for Coders
494 lines (395 loc) • 13.9 kB
JavaScript
/* ************************************************************************
qooxdoo - the new era of web development
http://qooxdoo.org
Copyright:
2004-2012 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:
* Christopher Zuendorf (czuendorf)
************************************************************************ */
/**
* Creates a drawer widget inside the given parent widget. The parent widget can
* be assigned as a constructor argument. If no parent is set, the application's
* root will be assumed as parent. A drawer widget can be assigned to left, right,
* top or bottom edge of its parent by property <code>orientation</code>. The drawer floats
* in on <code>show()</code> and floats out on <code>hide()</code>. Additionally the drawer is shown by
* swiping in reverse direction on the parent edge to where the drawer is placed
* to: Orientation: <code>left</code>, Swipe: <code>right</code> on parents edge: Drawer is shown etc.
* The drawer is hidden when user taps the parent area outside of the drawer.
* This behaviour can be deactivated by the property <code>hideOnParentTap</code>.
*
* <pre class='javascript'>
*
* var drawer = new qx.ui.mobile.container.Drawer();
* drawer.setOrientation("right");
* drawer.setTapOffset(100);
*
* var button = new qx.ui.mobile.form.Button("A Button");
* drawer.add(button);
* </pre>
*
*
*/
qx.Class.define("qx.ui.mobile.container.Drawer",
{
extend : qx.ui.mobile.container.Composite,
/*
*****************************************************************************
CONSTRUCTOR
*****************************************************************************
*/
/**
* @param parent {qx.ui.mobile.container.Composite?null} The widget to which
* the drawer should be added, if null it is added to app root.
* @param layout {qx.ui.mobile.layout.Abstract?null} The layout that should be
* used for this container.
*/
construct : function(parent, layout)
{
this.base(arguments);
if (layout) {
this.setLayout(layout);
}
this.initOrientation();
this.initPositionZ();
if (parent) {
if (qx.core.Environment.get("qx.debug")) {
this.assertInstance(parent, qx.ui.mobile.container.Composite);
}
parent.add(this);
} else {
qx.core.Init.getApplication().getRoot().add(this);
}
qx.core.Init.getApplication().addListener("back", this._onBack, this);
this.__parent = this.getLayoutParent();
this.__parent.addCssClass("drawer-parent");
this.__parent.addListener("swipe", this._onParentSwipe,this);
this.__parent.addListener("pointerdown", this._onParentPointerDown,this);
this.__pointerStartPosition = [0,0];
this.forceHide();
},
/*
*****************************************************************************
EVENTS
*****************************************************************************
*/
events :
{
/**
* Fired when the drawer changes its size.
*/
resize : "qx.event.type.Data"
},
/*
*****************************************************************************
PROPERTIES
*****************************************************************************
*/
properties : {
// overridden
defaultCssClass : {
refine : true,
init : "drawer"
},
/** Property for setting the orientation of the drawer.
* Allowed values are: <code>left</code>,<code>right</code>,<code>top</code>,<code>bottom</code> */
orientation : {
check : "String",
init : "left",
apply : "_applyOrientation"
},
/** The size of the drawer in <code>px</code>. This value is interpreted as width if
* orientation is <code>left | right</code>, as height if orientation is
* <code>top | bottom</code>. */
size : {
check : "Integer",
init : 300,
apply : "_applySize",
event : "resize"
},
/** Indicates whether the drawer should hide when the parent area of it is tapped. */
hideOnParentTap : {
check : "Boolean",
init : true
},
/**
* Indicates whether the drawer should hide when a back action appear form a key event.
*/
hideOnBack :
{
check : "Boolean",
init : true
},
/** Sets the size of the tapping area, where the drawer reacts on swipes for opening itself. */
tapOffset : {
check : "Integer",
init : 20
},
/** The duration time of the transition between shown/hidden state in ms. */
transitionDuration : {
check : "Integer",
init : 500,
apply : "_applyTransitionDuration"
},
/** Sets the drawer zIndex position relative to its parent. */
positionZ : {
check : [ "above", "below"],
init : "above",
apply : "_applyPositionZ"
}
},
/*
*****************************************************************************
MEMBERS
*****************************************************************************
*/
members :
{
__pointerStartPosition : null,
__parent : null,
__transitionEnabled : null,
__inTransition : null,
// property apply
_applyOrientation : function(value, old) {
this.removeCssClass(old);
this.addCssClass(value);
// Reapply width of height size depending on orientation.
this._applySize(this.getSize());
},
// property apply
_applyPositionZ : function(value,old) {
this.removeCssClass(old);
this.addCssClass(value);
if(this.__parent) {
this.__parent.setTranslateX(0);
this.__parent.setTranslateY(0);
}
},
// property apply
_applySize : function(value) {
var height = null;
var width = null;
var remSize = (value / 16);
if (this.getOrientation() == "left" || this.getOrientation() == "right") {
width = remSize + "rem";
} else {
height = remSize + "rem";
}
this._setStyle("height", height);
this._setStyle("width", width);
},
// property apply
_applyTransitionDuration : function(value,old) {
this.__transitionEnabled = value > 0;
},
/**
* Shows the drawer.
*/
show : function()
{
if(!this.isHidden() || this.__inTransition === true) {
return;
}
this.__inTransition = true;
// Make drawer visible before "changeVisibility" event is fired, after transition.
this._setStyle("visibility", "visible");
this.__parent.addCssClass("blocked");
if (this.getPositionZ() == "below") {
if (this.getOrientation() == "left") {
this.__parent.setTranslateX(this.getSize());
} else if (this.getOrientation() == "right") {
this.__parent.setTranslateX(-this.getSize());
} else if (this.getOrientation() == "top") {
this.__parent.setTranslateY(this.getSize());
} else if (this.getOrientation() == "bottom") {
this.__parent.setTranslateY(-this.getSize());
}
}
if (this.getTransitionDuration() > 0) {
this._enableTransition();
var callArguments = arguments;
var transitionTarget = this._getTransitionTarget().getContentElement();
var listenerId = qx.bom.Element.addListener(transitionTarget, "transitionEnd", function(evt) {
this.base(callArguments);
this._disableTransition();
this.__inTransition = false;
qx.bom.Element.removeListenerById(transitionTarget, listenerId);
}, this);
setTimeout(function() {
this.removeCssClass("hidden");
}.bind(this), 0);
} else {
this.base(arguments);
this.__inTransition = false;
this.removeCssClass("hidden");
}
},
/**
* Hides the drawer.
*/
hide : function() {
if(this.isHidden() || this.__inTransition === true) {
return;
}
this.__inTransition = true;
if (this.getPositionZ() == "below") {
this.__parent.setTranslateX(0);
this.__parent.setTranslateY(0);
}
if (this.getTransitionDuration() > 0) {
this._enableTransition();
var callArguments = arguments;
var transitionTarget = this._getTransitionTarget().getContentElement();
var listenerId = qx.bom.Element.addListener(transitionTarget, "transitionEnd", function(evt) {
this.base(callArguments);
this._disableTransition();
this.__parent.removeCssClass("blocked");
this.__inTransition = false;
qx.bom.Element.removeListenerById(transitionTarget, listenerId);
}, this);
setTimeout(function() {
this.addCssClass("hidden");
}.bind(this), 0);
} else {
this.base(arguments);
this.addCssClass("hidden");
this.__inTransition = false;
this.__parent.removeCssClass("blocked");
}
},
/**
* Strict way to hide this drawer. Removes the blocker from the parent,
* and hides the drawer without any animation. Should be called when drawer's
* parent is animated and drawer should hide immediately.
*/
forceHide : function() {
this._disableTransition();
if (this.getPositionZ() == "below") {
this.__parent.setTranslateX(0);
this.__parent.setTranslateY(0);
}
this.__parent.removeCssClass("blocked");
this.addCssClass("hidden");
},
// overridden
isHidden : function() {
return this.hasCssClass("hidden");
},
/**
* Enables the transition on this drawer.
*/
_enableTransition : function() {
qx.bom.element.Style.set(this._getTransitionTarget().getContentElement(), "transition", "all "+this.getTransitionDuration()+"ms ease-in-out");
},
/**
* Disables the transition on this drawer.
*/
_disableTransition : function() {
qx.bom.element.Style.set(this._getTransitionTarget().getContentElement(),"transition", null);
},
/**
* Returns the target widget which is responsible for the transition handling.
* @return {qx.ui.mobile.core.Widget} the transition target widget.
*/
_getTransitionTarget : function() {
if (this.getPositionZ() == "below") {
return this.__parent;
} else {
return this;
}
},
/**
* Toggle the visibility of the drawer.
* @return {Boolean} the new visibility state.
*/
toggleVisibility : function() {
if(this.isHidden()) {
this.show();
return true;
} else {
this.hide();
return false;
}
},
/**
* Handles a back event which appears on the application.
*
* @param evt {qx.event.type.Data} The back event.
*/
_onBack : function(evt)
{
var triggeredByKeyEvent = !!evt.getData();
if (triggeredByKeyEvent && !this.isHidden() && this.getHideOnBack())
{
evt.preventDefault();
this.hide();
}
},
/**
* Handles a tap on drawers' root.
* @param evt {qx.module.event.Pointer} Handled pointer event.
*/
_onParentPointerDown : function(evt) {
this.__pointerStartPosition = [evt.getViewportLeft(),evt.getViewportTop()];
var isShown = !this.hasCssClass("hidden");
if(isShown && this.isHideOnParentTap()) {
var location = qx.bom.element.Location.get(this.getContainerElement());
var orientation = this.getOrientation();
if (orientation == "left" && this.__pointerStartPosition[0] > location.right
|| orientation == "top" && this.__pointerStartPosition[1] > location.bottom
|| orientation == "bottom" && this.__pointerStartPosition[1] < location.top
|| orientation == "right" && this.__pointerStartPosition[0] < location.left)
{
// First event on overlayed page should be ignored.
evt.preventDefault();
this.hide();
}
}
},
/**
* Handles a swipe on layout parent.
* @param evt {qx.module.event.Pointer} Handled pointer event.
*/
_onParentSwipe : function(evt) {
var direction = evt.getDirection();
var isHidden = this.hasCssClass("hidden");
if(isHidden) {
var location = qx.bom.element.Location.get(this.getContainerElement());
if (
(direction == "right"
&& this.getOrientation() == "left"
&& this.__pointerStartPosition[0] < location.right + this.getTapOffset()
&& this.__pointerStartPosition[0] > location.right)
||
(direction == "left"
&& this.getOrientation() == "right"
&& this.__pointerStartPosition[0] > location.left - this.getTapOffset()
&& this.__pointerStartPosition[0] < location.left)
||
(direction == "down"
&& this.getOrientation() == "top"
&& this.__pointerStartPosition[1] < this.getTapOffset() + location.bottom
&& this.__pointerStartPosition[1] > location.bottom)
||
(direction == "up"
&& this.getOrientation() == "bottom"
&& this.__pointerStartPosition[1] > location.top - this.getTapOffset()
&& this.__pointerStartPosition[1] < location.top)
)
{
this.show();
}
}
}
},
destruct : function()
{
qx.core.Init.getApplication().removeListener("back", this._onBack, this);
this.__parent.removeListener("swipe", this._onParentSwipe, this);
this.__parent.removeListener("pointerdown", this._onParentPointerDown, this);
qx.util.DisposeUtil.destroyContainer(this);
this.__pointerStartPosition = this.__parent = this.__transitionEnabled = null;
}
});