UNPKG

@qooxdoo/framework

Version:

The JS Framework for Coders

326 lines (263 loc) 8.64 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) ************************************************************************ */ /** * @require(qx.module.Animation) * @require(qx.module.Manipulating) * * Mixin for the {@link Scroll} container. Used when the variant * <code>qx.mobile.nativescroll</code> is set to "on". */ qx.Mixin.define("qx.ui.mobile.container.MNativeScroll", { construct : function() { this.addCssClass("native"); this._snapPoints = []; this.addListenerOnce("appear", this._onAppear, this); this.addListener("trackstart", this._onTrackStart, this); this.addListener("trackend", this._onTrackEnd, this); qx.bom.Event.addNativeListener(this._getContentElement(), "scroll", this._onScroll.bind(this)); if (qx.core.Environment.get("os.name") == "ios") { this.addListener("touchmove", this._onTouchMove, this); } }, members : { _snapPoints : null, _onTrack : null, _snapTimeoutId : null, /** * Event handler for <code>appear</code> event. */ _onAppear: function() { this._calcSnapPoints(); }, /** * Event handler for <code>touchmove</code> event. * Needed for preventing iOS page bounce. * @param evt {qx.event.type.Touch} touchmove event. */ _onTouchMove : function(evt) { // If scroll container is scrollable if (this._isScrollableY()) { evt.stopPropagation(); } else { evt.preventDefault(); } }, /** * Event handler for <code>trackstart</code> events. * @param evt {qx.event.type.Track} touchmove event. */ _onTrackStart: function(evt) { this._onTrack = true; if (qx.core.Environment.get("os.name") == "ios") { this._preventPageBounce(); } }, /** * Prevents the iOS page bounce if scroll container reaches the upper or lower vertical scroll limit. */ _preventPageBounce: function() { // If scroll container is scrollable if (this._isScrollableY()) { var element = this.getContentElement(); var scrollTop = element.scrollTop; var maxScrollTop = element.scrollHeight - this.getLayoutParent().getContentElement().offsetHeight; if (scrollTop === 0) { element.scrollTop = 1; } else if (scrollTop == maxScrollTop) { element.scrollTop = maxScrollTop - 1; } } }, /** * Event handler for <code>trackend</code> events. * @param evt {qx.event.type.Track} touchmove event. */ _onTrackEnd: function(evt) { this._onTrack = false; if(this._snapTimeoutId) { clearTimeout(this._snapTimeoutId); } this._snapTimeoutId = setTimeout(function() { this._snap(); }.bind(this), 100); evt.stopPropagation(); }, /** * Event handler for <code>scroll</code> events. */ _onScroll : function() { var scrollLeft = this.getContentElement().scrollLeft; var scrollTop = this.getContentElement().scrollTop; this._setCurrentX(scrollLeft); this._setCurrentY(scrollTop); if(this._snapTimeoutId) { clearTimeout(this._snapTimeoutId); } this._snapTimeoutId = setTimeout(function() { if(!this._onTrack) { this._snap(); } }.bind(this), 100); }, /** * Calculates the snapping points for the x/y axis. */ _calcSnapPoints: function() { if (this._scrollProperties) { var snap = this._scrollProperties.snap; if (snap) { this._snapPoints = []; var snapTargets = this.getContentElement().querySelectorAll(snap); for (var i = 0; i < snapTargets.length; i++) { var snapPoint = qx.bom.element.Location.getRelative(this._getContentElement(), snapTargets[i], "scroll", "scroll"); this._snapPoints.push(snapPoint); } } } }, /** * Determines the next snap points for the passed current position. * @param current {Integer} description * @param snapProperty {String} "top" or "left" * @return {Integer} the determined snap point. */ _determineSnapPoint: function(current, snapProperty) { for (var i = 0; i < this._snapPoints.length; i++) { var snapPoint = this._snapPoints[i]; if (current <= -snapPoint[snapProperty]) { if (i > 0) { var previousSnapPoint = this._snapPoints[i - 1]; var previousSnapDiff = Math.abs(current + previousSnapPoint[snapProperty]); var nextSnapDiff = Math.abs(current + snapPoint[snapProperty]); if (previousSnapDiff < nextSnapDiff) { return -previousSnapPoint[snapProperty]; } else { return -snapPoint[snapProperty]; } } else { return -snapPoint[snapProperty]; } } } return current; }, /** * Snaps the scrolling area to the nearest snap point. */ _snap : function() { this.fireEvent("scrollEnd"); var element = this.getContentElement(); if(element.scrollTop < 1 || element.scrollTop > this._getScrollHeight()) { return; } var current = this._getPosition(); var nextX = this._determineSnapPoint(current[0], "left"); var nextY = this._determineSnapPoint(current[1], "top"); if (nextX != current[0] || nextY != current[1]) { this._scrollTo(nextX, nextY, 300); } }, /** * Refreshes the scroll container. Recalculates the snap points. */ _refresh : function() { this._calcSnapPoints(); }, /** * Mixin method. Creates the scroll element. * * @return {Element} The scroll element */ _createScrollElement: function() { return null; }, /** * Returns the current scroll position * @return {Array} an array with <code>[scrollLeft,scrollTop]</code>. */ _getPosition: function() { return [this.getContentElement().scrollLeft, this.getContentElement().scrollTop]; }, /** * Mixin method. Returns the scroll content element. * * @return {Element} The scroll content element */ _getScrollContentElement: function() { return null; }, /** * Returns the scrolling height of the inner container. * @return {Number} the scrolling height. */ _getScrollHeight : function() { if(!this.getContentElement()) { return 0; } return this.getContentElement().scrollHeight - this.getContentElement().offsetHeight; }, /** * Returns the scrolling width of the inner container. * @return {Number} the scrolling width. */ _getScrollWidth : function() { if(!this.getContentElement()) { return 0; } return this.getContentElement().scrollWidth - this.getContentElement().offsetWidth; }, /** * Scrolls the wrapper contents to the x/y coordinates in a given period. * * @param x {Integer} X coordinate to scroll to. * @param y {Integer} Y coordinate to scroll to. * @param time {Integer} is always <code>0</code> for this mixin. */ _scrollTo: function(x, y, time) { var element = this.getContentElement(); if(!time) { element.scrollLeft = x; element.scrollTop = y; return; } var startY = element.scrollTop; var startX = element.scrollLeft; if (element) { qx.bom.element.Animation.animate(element, { "duration": time, "keyFrames": { 0: { "scrollTop": startY, "scrollLeft": startX }, 100: { "scrollTop": y, "scrollLeft": x } }, "keep": 100, "timing": "ease-out" }); } } }, destruct : function() { qx.bom.Event.removeNativeListener(this._getContentElement(), "scroll", this._onScroll.bind(this)); this.removeListener("touchmove", this._onTouchMove, this); this.removeListener("appear", this._onAppear, this); this.removeListener("trackstart", this._onTrackStart, this); this.removeListener("trackend", this._onTrackEnd, this); } });