@qooxdoo/framework
Version:
The JS Framework for Coders
497 lines (425 loc) • 14.1 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(parent, layout) {
super();
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(value, old) {
this.removeCssClass(old);
this.addCssClass(value);
// Reapply width of height size depending on orientation.
this._applySize(this.getSize());
},
// property apply
_applyPositionZ(value, old) {
this.removeCssClass(old);
this.addCssClass(value);
if (this.__parent) {
this.__parent.setTranslateX(0);
this.__parent.setTranslateY(0);
}
},
// property apply
_applySize(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(value, old) {
this.__transitionEnabled = value > 0;
},
/**
* Shows the drawer.
*/
show() {
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) {
super.show();
this._disableTransition();
this.__inTransition = false;
qx.bom.Element.removeListenerById(transitionTarget, listenerId);
},
this
);
setTimeout(
function () {
this.removeCssClass("hidden");
}.bind(this),
0
);
} else {
super.show();
this.__inTransition = false;
this.removeCssClass("hidden");
}
},
/**
* Hides the drawer.
*/
hide() {
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) {
super.hide();
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 {
super.hide();
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() {
this._disableTransition();
if (this.getPositionZ() == "below") {
this.__parent.setTranslateX(0);
this.__parent.setTranslateY(0);
}
this.__parent.removeCssClass("blocked");
this.addCssClass("hidden");
},
// overridden
isHidden() {
return this.hasCssClass("hidden");
},
/**
* Enables the transition on this drawer.
*/
_enableTransition() {
qx.bom.element.Style.set(
this._getTransitionTarget().getContentElement(),
"transition",
"all " + this.getTransitionDuration() + "ms ease-in-out"
);
},
/**
* Disables the transition on this drawer.
*/
_disableTransition() {
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() {
if (this.getPositionZ() == "below") {
return this.__parent;
} else {
return this;
}
},
/**
* Toggle the visibility of the drawer.
* @return {Boolean} the new visibility state.
*/
toggleVisibility() {
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(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(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(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() {
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;
}
});