UNPKG

@qooxdoo/framework

Version:

The JS Framework for Coders

538 lines (470 loc) 15 kB
/* ************************************************************************ 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: * Tino Butz (tbtz) ************************************************************************ */ /** * Container, which allows, depending on the set variant <code>qx.mobile.nativescroll</code>, * vertical and horizontal scrolling if the contents is larger than the container. * * Note that this class can only have one child widget. This container has a * fixed layout, which cannot be changed. * * *Example* * * Here is a little example of how to use the widget. * * <pre class='javascript'> * // create the scroll widget * var scroll = new qx.ui.mobile.container.Scroll(); * * // add a children * scroll.add(new qx.ui.mobile.basic.Label("Name: ")); * * this.getRoot().add(scroll); * </pre> * * This example creates a scroll container and adds a label to it. */ qx.Class.define("qx.ui.mobile.container.Scroll", { extend: qx.ui.mobile.container.Composite, /** * @param scrollProperties {Object} A map with scroll properties which are passed to the scrolling container (may contain iScroll properties). */ construct(scrollProperties) { super(); if (scrollProperties) { this._scrollProperties = scrollProperties; } this.addListener("appear", this._updateWaypoints, this); this._waypointsX = []; this._waypointsY = []; this._currentX = 0; this._currentY = 0; }, events: { /** Fired when the scroll container reaches its end position (including momentum/inertia). */ scrollEnd: "qx.event.type.Event", /** Fired when the user scrolls to the end of scroll area. */ pageEnd: "qx.event.type.Event", /** Fired when a vertical or horizontal waypoint is triggered. Data: * <code> {"offset": 0, * "input": "10%", * "index": 0, * "element" : 0}</code> */ waypoint: "qx.event.type.Data", /** * Fired when a momentum starts on an iOS device. */ momentumStart: "qx.event.type.Event", /** * Fired when a momentum ends on an iOS device. */ momentumEnd: "qx.event.type.Data" }, /* ***************************************************************************** PROPERTIES ***************************************************************************** */ properties: { // overridden defaultCssClass: { refine: true, init: "scroll" }, /** * Delegation object which can have one or more functions defined by the * {@link qx.ui.mobile.container.IScrollDelegate} interface. * * @internal */ delegate: { init: null, nullable: true } }, /* ***************************************************************************** MEMBERS ***************************************************************************** */ members: { _scrollProperties: null, _activeWaypointX: null, _activeWaypointY: null, _waypointsX: null, _waypointsY: null, _calculatedWaypointsX: null, _calculatedWaypointsY: null, _currentX: null, _currentY: null, /** * Sets the current x position. * @param value {Number} the current horizontal position. */ _setCurrentX(value) { var old = this._currentX; this._currentX = value; this._fireWaypoint(value, old, "x"); }, /** * Sets the current y position. * @param value {Number} the current vertical position. */ _setCurrentY(value) { var old = this._currentY; this._currentY = value; this._fireWaypoint(value, old, "y"); }, /** * Sets the horizontal trigger points, where a <code>waypoint</code> event will be fired. * @param waypoints {Array} description */ setWaypointsX(waypoints) { this._waypointsX = waypoints; }, /** * Sets the vertical trigger points, where a <code>waypoint</code> event will be fired. * @param waypoints {Array} an array with waypoint descriptions. Allowed are percentage description as string, or pixel trigger points defined as numbers. <code>["20%",200]</code> */ setWaypointsY(waypoints) { this._waypointsY = waypoints; }, /** * Returns the scroll height. * @return {Number} the scroll height. */ getScrollHeight() { return this._getScrollHeight(); }, /** * Returns the scroll width. * @return {Number} the scroll width. */ getScrollWidth() { return this._getScrollWidth(); }, /** * Re-calculates the internal waypoint offsets. */ _updateWaypoints() { this._calculatedWaypointsX = []; this._calculatedWaypointsY = []; this._calcWaypoints( this._waypointsX, this._calculatedWaypointsX, this.getScrollWidth(), "x" ); this._calcWaypoints( this._waypointsY, this._calculatedWaypointsY, this.getScrollHeight() ); }, /** * Validates and checks the waypoint offsets. * @param waypoints {Array} an array with waypoint descriptions. * @param results {Array} the array where calculated waypoints will be added. * @param scrollSize {Number} the vertical or horizontal scroll size. * @param axis {String?} "x" or "y". */ _calcWaypoints(waypoints, results, scrollSize, axis) { axis = axis || "y"; var offset = 0; for (var i = 0; i < waypoints.length; i++) { var waypoint = waypoints[i]; if (qx.lang.Type.isString(waypoint)) { if (waypoint.endsWith("%")) { offset = parseInt(waypoint, 10) * (scrollSize / 100); results.push({ offset: offset, input: waypoint, index: i, element: null, axis: axis }); } else { // Dynamically created waypoints, based upon a selector. var element = this.getContentElement(); var waypointElements = qx.bom.Selector.query(waypoint, element); for (var j = 0; j < waypointElements.length; j++) { var position = qx.bom.element.Location.getRelative( waypointElements[j], element ); if (axis === "y") { offset = position.top + this.getContentElement().scrollTop; } else if (axis === "x") { offset = position.left + this.getContentElement().scrollLeft; } results.push({ offset: position.top + this._currentY, input: waypoint, index: i, element: j, axis: axis }); } } } else if (qx.lang.Type.isNumber(waypoint)) { results.push({ offset: waypoint, input: waypoint, index: i, element: null, axis: axis }); } } results.sort(function (a, b) { return a.offset - b.offset; }); }, /** * Fires a waypoints event when scroll position changes. * @param value {Number} old scroll position. * @param old {Number} old scroll position. * @param axis {String} "x" or "y". */ _fireWaypoint(value, old, axis) { var waypoints = this._calculatedWaypointsY; if (axis === "x") { waypoints = this._calculatedWaypointsX; } if (waypoints === null) { return; } var nextWaypoint = null; for (var i = 0; i < waypoints.length; i++) { var waypoint = waypoints[i]; if (waypoint.offset !== null) { if ( (value > -1 && value >= waypoint.offset) || (value < 0 && waypoint.offset < 0 && value <= waypoint.offset) ) { nextWaypoint = waypoint; } else { break; } } } if (nextWaypoint === null) { if (axis === "x") { this._activeWaypointX = null; } else { this._activeWaypointY = null; } return; } var direction = null; if (old <= value) { direction = "down"; if (axis == "x") { direction = "left"; } } else { direction = "up"; if (axis == "x") { direction = "right"; } } var activeWaypoint = this._activeWaypointY; if (axis === "x") { activeWaypoint = this._activeWaypointX; } if ( activeWaypoint === null || activeWaypoint.index !== nextWaypoint.index || activeWaypoint.element !== nextWaypoint.element ) { activeWaypoint = nextWaypoint; this._activeWaypointY = activeWaypoint; if (axis === "x") { this._activeWaypointX = activeWaypoint; } this.fireDataEvent("waypoint", { axis: axis, index: nextWaypoint.index, element: nextWaypoint.element, direction: direction }); } }, // overridden _createContainerElement() { var element = super._createContainerElement(); var scrollElement = this._createScrollElement(); if (scrollElement) { return scrollElement; } return element; }, // overridden _getContentElement() { var contentElement = super._getContentElement(); var scrollContentElement = this._getScrollContentElement(); return scrollContentElement || contentElement; }, /** * Calls the refresh function the used scrolling method. Needed to recalculate the * scrolling container. */ refresh() { this._refresh(); this._updateWaypoints(); }, /** * Scrolls the wrapper contents to the x/y coordinates in a given time. * * @param x {Integer} X coordinate to scroll to. * @param y {Integer} Y coordinate to scroll to. * @param time {Integer} Time slice in which scrolling should * be done. */ scrollTo(x, y, time) { this._scrollTo(x, y, time); }, /** * Returns the current scroll position * @return {Array} an array with <code>[scrollLeft,scrollTop]</code>. */ getPosition() { return this._getPosition(); }, /** * Detects whether this scroll container is scrollable or not. * @return {Boolean} <code>true</code> or <code>false</code> */ isScrollable() { return this._isScrollable(); }, /** * Detects whether this scroll container is scrollable or not. * @return {Boolean} <code>true</code> or <code>false</code> */ _isScrollable() { return this._isScrollableX() || this._isScrollableY(); }, /** * Detects whether this scroll container is scrollable on x axis or not. * @return {Boolean} <code>true</code> or <code>false</code> */ _isScrollableX() { if (this.getLayoutParent() === null) { return false; } var parentWidth = this.getContainerElement().clientWidth; var contentWidth = this.getContentElement().scrollWidth; var scrollContentElement = this._getScrollContentElement(); if (scrollContentElement) { contentWidth = qx.bom.element.Dimension.getWidth(scrollContentElement); } return parentWidth < contentWidth; }, /** * Detects whether this scroll container is scrollable on y axis or not. * @return {Boolean} <code>true</code> or <code>false</code> */ _isScrollableY() { if (this.getLayoutParent() === null) { return false; } var parentHeight = this.getContainerElement().clientHeight; var contentHeight = this.getContentElement().scrollHeight; var scrollContentElement = this._getScrollContentElement(); if (scrollContentElement) { contentHeight = qx.bom.element.Dimension.getHeight(scrollContentElement); } return parentHeight < contentHeight; }, /** * Scrolls the wrapper contents to the widgets coordinates in a given * period. * * @param target {Element} the element to which the scroll container should scroll to. * @param time {Integer?0} Time slice in which scrolling should * be done (in seconds). * */ scrollToElement(target, time) { this._scrollToElement(target, time); }, /** * Scrolls the wrapper contents to the widgets coordinates in a given * period. * * @param element {String} the element to which the scroll container should scroll to. * @param time {Integer?0} Time slice in which scrolling should be done (in seconds). * */ _scrollToElement(element, time) { if (this._getContentElement() && this._isScrollable()) { if (typeof time === "undefined") { time = 0; } var location = qx.bom.element.Location.getRelative( this._getContentElement(), element, "scroll", "scroll" ); var offset = this._getScrollOffset(); this._scrollTo( -location.left - offset[0], -location.top - offset[1], time ); } }, /** * * Determines the scroll offset for the <code>_scrollToElement</code> method. * If a delegate is available, the method calls * <code>qx.ui.mobile.container.IScrollDelegate.getScrollOffset()</code> for offset calculation. * * @return {Array} an array with x,y offset. */ _getScrollOffset() { var delegate = this.getDelegate(); if (delegate != null && delegate.getScrollOffset) { return delegate.getScrollOffset.bind(this)(); } else { return [0, 0]; } }, /** * Scrolls the wrapper contents to the widgets coordinates in a given * period. * * @param widget {qx.ui.mobile.core.Widget} the widget, the scroll container should scroll to. * @param time {Integer} Time slice in which scrolling should * be done. */ scrollToWidget(widget, time) { if (widget) { this._scrollToElement(widget.getContentElement(), time); } } }, defer(statics) { if (qx.core.Environment.get("qx.mobile.nativescroll") == false) { qx.Class.include(statics, qx.ui.mobile.container.MIScroll); } else { qx.Class.include(statics, qx.ui.mobile.container.MNativeScroll); } }, destruct() { this.removeListener("appear", this._updateWaypoints, this); this._waypointsX = this._waypointsY = null; } });