+/**
+ *
+ * usage:
+ *
+ * new Roo.ux.TouchScroll( { el : id| dom element | roo element } )
+ *
+ *
+ */
+
+
+Roo.ux.TouchScroll = function (cfg) {
+ Roo.apply(this,cfg);
+ this.el = Roo.get(this.el);
+ this.init();
+}
+
+Roo.apply(Roo.ux.TouchScroll.prototype, {
+
+ // Define default scroll settings
+
+ y: 0,
+ scrollHeight: 0,
+ elastic: !navigator.userAgent.match(/android/i),
+ momentum: true,
+ elasticDamp: 0.6,
+ elasticTime: 50,
+ reboundTime: 400,
+ momentumDamp: 0.9,
+ momentumTime: 300,
+ iPadMomentumDamp: 0.95,
+ iPadMomentumTime: 1200,
+ touchTags: ['select', 'input', 'textarea'],
+
+ _init : false,
+
+ init: function( ) {
+
+
+ // Prevent double-init, just update instead
+ if (this._init) {
+ return this.update();
+ }
+
+ this._init = true;
+
+ // Define element variables
+ var
+ scrollY = -this.el.getY(),
+ touchY = 0,
+ movedY = 0,
+ pollY = 0,
+ height = 0,
+ maxHeight = 0,
+ scrollHeight = 0,
+ scrolling = false,
+ bouncing = false,
+ moved = false,
+ timeoutID,
+ isiPad = navigator.platform.match(/ipad/i) ? true : false,
+ hasMatrix = 'WebKitCSSMatrix' in window,
+ has3d = hasMatrix && 'm11' in new WebKitCSSMatrix();
+
+ // Keep bottom of scroll area at the bottom on resize
+ var update = this.update = function() {
+ height = el.getHeight();
+ if (el.dom.scrollHeight) {
+ scrollHeight = el.dom.scrollHeight;
+ } else {
+ scrollHeight = el.dom.getAttribute('scrollHeight');
+ }
+ if (scrollHeight < height) {
+ scrollHeight = height;
+ }
+ maxHeight = height - scrollHeight;
+ clearTimeout(timeoutID);
+ clampScroll(false);
+ };
+
+ // Set up initial variables
+ update();
+
+ // Set up transform CSS
+ this.el.setStyle({
+ '-webkit-transition-property': '-webkit-transform',
+ '-webkit-transition-timing-function': 'cubic-bezier(0,0,0.2,1)',
+ '-webkit-transition-duration': '0',
+ '-webkit-transform': cssTranslate(scrollY)
+ });
+
+ // Listen for screen size change event
+ window.addEventListener('onorientationchange' in window ?
+ 'orientationchange' : 'resize', update, false);
+
+ // Listen for touch events
+ this.el.on('touchstart.touchScroll', touchStart);
+ this.el.on('touchmove.touchScroll', touchMove);
+ this.el.on('touchend.touchScroll touchcancel.touchScroll', touchEnd);
+ this.el.on('webkitTransitionEnd.touchScroll', transitionEnd);
+
+ // Set the position of the scroll area using transform CSS
+ var setPosition = this.setPosition = function(y) {
+ scrollY = y;
+ this.el.setStyle('-webkit-transform', cssTranslate(scrollY));
+ };
+
+ // Transform using a 3D translate if available
+ function cssTranslate(y) {
+ return 'translate' + (has3d ? '3d(0,' : '(0,') + y + 'px' + (has3d ? ',0)' : ')');
+ }
+
+ // Set CSS transition time
+ function setTransitionTime(time) {
+ time = time || '0';
+ this.el.setStyle('-webkit-transition-duration', time + 'ms');
+ }
+
+ // Get the actual pixel position made by transform CSS
+ function getPosition() {
+ if (hasMatrix) {
+ var transform = window.getComputedStyle(this.el.dom).webkitTransform;
+ if (!!transform && transform !== 'none') {
+ var matrix = new WebKitCSSMatrix(transform);
+ return matrix.f;
+ }
+ }
+ return scrollY;
+ }
+
+ // Expose getPosition API
+ this.getPosition = function() {
+ return getPosition();
+ };
+
+ // Bounce back to the bounds after momentum scrolling
+ function reboundScroll() {
+ if (scrollY > 0) {
+ scrollTo(0, o.reboundTime);
+ } else if (scrollY < maxHeight) {
+ scrollTo(maxHeight, o.reboundTime);
+ }
+ }
+
+ // Stop everything once the CSS transition in complete
+ function transitionEnd() {
+ if (bouncing) {
+ bouncing = false;
+ reboundScroll();
+ }
+
+ clearTimeout(timeoutID);
+ }
+
+ // Limit the scrolling to within the bounds
+ function clampScroll(poll) {
+ if (!hasMatrix || bouncing) {
+ return;
+ }
+
+ var oldY = pollY;
+ pollY = getPosition();
+
+ if (pollY > 0) {
+ if (o.elastic) {
+ // Slow down outside top bound
+ bouncing = true;
+ scrollY = 0;
+ momentumScroll(pollY - oldY, o.elasticDamp, 1, height, o.elasticTime);
+ } else {
+ // Stop outside top bound
+ setTransitionTime(0);
+ setPosition(0);
+ }
+ } else if (pollY < maxHeight) {
+ if (o.elastic) {
+ // Slow down outside bottom bound
+ bouncing = true;
+ scrollY = maxHeight;
+ momentumScroll(pollY - oldY, o.elasticDamp, 1, height, o.elasticTime);
+ } else {
+ // Stop outside bottom bound
+ setTransitionTime(0);
+ setPosition(maxHeight);
+ }
+ } else if (poll) {
+ // Poll the computed position to check if element is out of bounds
+ timeoutID = setTimeout(clampScroll, 20, true);
+ }
+ }
+
+ // Animate to a position using CSS
+ function scrollTo(destY, time) {
+ if (destY === scrollY) {
+ return;
+ }
+
+ moved = true;
+ setTransitionTime(time);
+ setPosition(destY);
+ }
+
+ // Perform a momentum-based scroll using CSS
+ function momentumScroll(d, k, minDist, maxDist, t) {
+ var ad = Math.abs(d),
+ dy = 0;
+
+ // Calculate the total distance
+ while (ad > 0.1) {
+ ad *= k;
+ dy += ad;
+ }
+
+ // Limit to within min and max distances
+ if (dy > maxDist) {
+ dy = maxDist;
+ }
+ if (dy > minDist) {
+ if (d < 0) {
+ dy = -dy;
+ }
+
+ dy += scrollY;
+
+ // If outside the bounds, don't go too far
+ if (height > 0) {
+ if (dy > height * 2) {
+ var ody = dy;
+ dy = height * 2;
+ } else if (dy < maxHeight - height * 2) {
+ dy = maxHeight - height * 2;
+ }
+ }
+
+ // Perform scroll
+ scrollTo(Math.round(dy), t);
+ }
+
+ clampScroll(true);
+ }
+
+ // Get the touch points from this event
+ function getTouches(e) {
+ if (e.originalEvent) {
+ if (e.originalEvent.touches && e.originalEvent.touches.length) {
+ return e.originalEvent.touches;
+ } else if (e.originalEvent.changedTouches && e.originalEvent.changedTouches.length) {
+ return e.originalEvent.changedTouches;
+ }
+ }
+ return e.touches;
+ }
+
+ // Dispatches a fake mouse event from a touch event
+ function dispatchMouseEvent(name, touch, target) {
+ var e = document.createEvent('MouseEvent');
+ e.initMouseEvent(name, true, true, touch.view, 1, touch.screenX, touch.screenY, touch.clientX, touch.clientY, false, false, false, false, 0, null);
+ target.dispatchEvent(e);
+ }
+
+ // Find the root node of this target
+ function getRootNode(target) {
+ while (target.nodeType !== 1) {
+ target = target.parentNode;
+ }
+ return target;
+ }
+
+ // Perform a touch start event
+ function touchStart(e) {
+ // Allow certain HTML tags to receive touch events
+ if ($.inArray(e.target.tagName.toLowerCase(), o.touchTags) !== -1) {
+ return;
+ }
+
+ // Stop the default touches
+ e.preventDefault();
+ e.stopPropagation();
+
+ var touch = getTouches(e)[0];
+
+ // Dispatch a fake mouse down event
+ dispatchMouseEvent('mousedown', touch, getRootNode(touch.target));
+
+ scrolling = true;
+ moved = false;
+ movedY = 0;
+
+ clearTimeout(timeoutID);
+ setTransitionTime(0);
+
+ // Check scroll position
+ if (o.momentum) {
+ var y = getPosition();
+ if (y !== scrollY) {
+ setPosition(y);
+ moved = true;
+ }
+ }
+
+ touchY = touch.pageY - scrollY;
+ }
+
+ // Perform a touch move event
+ function touchMove(e) {
+ if (!scrolling) {
+ return;
+ }
+
+ var dy = getTouches(e)[0].pageY - touchY;
+
+ // Elastic-drag or stop when moving outside of boundaries
+ if (dy > 0) {
+ if (o.elastic) {
+ dy /= 2;
+ } else {
+ dy = 0;
+ }
+ } else if (dy < maxHeight) {
+ if (o.elastic) {
+ dy = (dy + maxHeight) / 2;
+ } else {
+ dy = maxHeight;
+ }
+ }
+
+ movedY = dy - scrollY;
+ moved = true;
+ setPosition(dy);
+ }
+
+ // Perform a touch end event
+ function touchEnd(e) {
+ if (!scrolling) {
+ return;
+ }
+
+ scrolling = false;
+
+ if (moved) {
+ // Ease back to within boundaries
+ if (scrollY > 0 || scrollY < maxHeight) {
+ reboundScroll();
+ } else if (o.momentum) {
+ // Free scroll with momentum
+ momentumScroll(movedY, isiPad ? o.iPadMomentumDamp : o.momentumDamp, 40, 2000, isiPad ? o.iPadMomentumTime : o.momentumTime);
+ }
+ } else {
+ var touch = getTouches(e)[0],
+ target = getRootNode(touch.target);
+
+ // Dispatch fake mouse up and click events if this touch event did not move
+ dispatchMouseEvent('mouseup', touch, target);
+ dispatchMouseEvent('click', touch, target);
+ }
+ }
+
+ });
+ },
+
+ update: function() {
+ return this.each(function() {
+ this.update();
+ });
+ },
+
+ getPosition: function() {
+ var a = [];
+ this.each(function() {
+ a.push(-this.getPosition());
+ });
+ return a;
+ },
+
+ setPosition: function(y) {
+ return this.each(function() {
+ this.setPosition(-y);
+ });
+ }
+
+};
+
+ // Public method for touchScroll
+ $.fn.touchScroll = function(method) {
+ if (methods[method]) {
+ return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
+ } else if (typeof method === 'object' || !method) {
+ return methods.init.apply(this, arguments);
+ } else {
+ $.error('Method ' + method + ' does not exist on jQuery.touchScroll');
+ }
+ };
+
+})(jQuery);
\ No newline at end of file