UNPKG

@qooxdoo/framework

Version:

The JS Framework for Coders

549 lines (452 loc) 15.1 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 : function(scrollProperties) { this.base(arguments); 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 : function(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 : function(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 : function(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 : function(waypoints) { this._waypointsY = waypoints; }, /** * Returns the scroll height. * @return {Number} the scroll height. */ getScrollHeight: function() { return this._getScrollHeight(); }, /** * Returns the scroll width. * @return {Number} the scroll width. */ getScrollWidth: function() { return this._getScrollWidth(); }, /** * Re-calculates the internal waypoint offsets. */ _updateWaypoints: function() { 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: function(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: function(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: function() { var element = this.base(arguments); var scrollElement = this._createScrollElement(); if (scrollElement) { return scrollElement; } return element; }, // overridden _getContentElement: function() { var contentElement = this.base(arguments); var scrollContentElement = this._getScrollContentElement(); return scrollContentElement || contentElement; }, /** * Calls the refresh function the used scrolling method. Needed to recalculate the * scrolling container. */ refresh: function() { 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: function(x, y, time) { this._scrollTo(x, y, time); }, /** * Returns the current scroll position * @return {Array} an array with <code>[scrollLeft,scrollTop]</code>. */ getPosition: function() { return this._getPosition(); }, /** * Detects whether this scroll container is scrollable or not. * @return {Boolean} <code>true</code> or <code>false</code> */ isScrollable: function() { return this._isScrollable(); }, /** * Detects whether this scroll container is scrollable or not. * @return {Boolean} <code>true</code> or <code>false</code> */ _isScrollable: function() { 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: function() { 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: function() { 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: function(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 : function(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 : function() { 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: function(widget, time) { if (widget) { this._scrollToElement(widget.getContentElement(), time); } } }, defer : function(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 : function() { this.removeListener("appear", this._updateWaypoints, this); this._waypointsX = this._waypointsY = null; } });