Ext.gesture.Manager = new Ext.AbstractManager({ startEventName: 'touchstart', moveEventName: 'touchmove', endEventName: 'touchend', init: function() { this.targets = []; if (!Ext.supports.Touch) { Ext.apply(this, { startEventName: 'mousedown', moveEventName: 'mousemove', endEventName: 'mouseup' }); } this.followTouches = []; this.currentGestures = []; this.currentTargets = []; document.addEventListener(this.startEventName, Ext.createDelegate(this.onTouchStart, this), true); document.addEventListener(this.endEventName, Ext.createDelegate(this.onTouchEnd, this), true); }, onTouchStart: function(e) { // There's already a touchstart without any touchend! // This used to happen on HTC Desire and HTC Incredible // We have to clean it up if (this.startEvent) { this.onTouchEnd(e); } var targets = [], target = e.target; this.locks = {}; this.currentTargets = [target]; while (target) { if (this.targets.indexOf(target) != -1) { targets.unshift(target); } target = target.parentNode; this.currentTargets.push(target); } this.startEvent = e; this.startPoint = Ext.util.Point.fromEvent(e); this.handleTargets(targets, e); }, /** * This listener is here to always ensure we stop all current gestures * @private */ onTouchEnd: function(e) { var gestures = this.currentGestures.slice(0), ln = gestures.length, i, gesture, endPoint, hasMoved = false, touch = e.changedTouches ? e.changedTouches[0] : e; if (this.startPoint) { endPoint = Ext.util.Point.fromEvent(e); // The point has changed, we should execute another onTouchMove before onTouchEnd // to deal with the problem of missing events on Androids and alike // This significantly improves scrolling experience on Androids! Yeah! if (!this.startPoint.equals(endPoint)) { hasMoved = true; } } this.followTouches = []; this.startedChangedTouch = false; this.currentTargets = []; this.startEvent = null; this.startPoint = null; for (i = 0; i < ln; i++) { gesture = gestures[i]; if (!e.stopped && gesture.listenForEnd) { if (hasMoved) { gesture.onTouchMove(e, touch); } gesture.onTouchEnd(e, touch); } this.stopGesture(gesture); } }, startGesture: function(gesture) { var me = this; gesture.started = true; if (gesture.listenForMove) { gesture.onTouchMoveWrap = function(e) { if (!e.stopped) { gesture.onTouchMove(e, e.changedTouches ? e.changedTouches[0] : e); } }; gesture.target.addEventListener(me.moveEventName, gesture.onTouchMoveWrap, !!gesture.capture); } this.currentGestures.push(gesture); }, stopGesture: function(gesture) { gesture.started = false; if (gesture.listenForMove) { gesture.target.removeEventListener(this.moveEventName, gesture.onTouchMoveWrap, !!gesture.capture); } this.currentGestures.remove(gesture); }, handleTargets: function(targets, e) { // In handle targets we have to first handle all the capture targets, // then all the bubble targets. var ln = targets.length, i, target; this.startedChangedTouch = false; this.startedTouches = Ext.supports.Touch ? e.touches : [e]; for (i = 0; i < ln; i++) { if (e.stopped) { break; } target = targets[i]; this.handleTarget(target, e, true); } for (i = ln - 1; i >= 0; i--) { if (e.stopped) { break; } target = targets[i]; this.handleTarget(target, e, false); } if (this.startedChangedTouch) { this.followTouches = this.followTouches.concat((Ext.supports.Touch && e.targetTouches) ? Ext.toArray(e.targetTouches) : [e]); } }, handleTarget: function(target, e, capture) { var gestures = Ext.Element.data(target, 'x-gestures') || [], ln = gestures.length, i, gesture; for (i = 0; i < ln; i++) { gesture = gestures[i]; if ( (!!gesture.capture === !!capture) && (this.followTouches.length < gesture.touches) && ((Ext.supports.Touch && e.targetTouches) ? (e.targetTouches.length === gesture.touches) : true) ) { this.startedChangedTouch = true; this.startGesture(gesture); if (gesture.listenForStart) { gesture.onTouchStart(e, e.changedTouches ? e.changedTouches[0] : e); } if (e.stopped) { break; } } } }, addEventListener: function(target, eventName, listener, options) { target = Ext.getDom(target); var targets = this.targets, name = this.getGestureName(eventName), gestures = Ext.Element.data(target, 'x-gestures') || [], gesture; //if (!name) { throw new Error('Trying to subscribe to unknown event ' + eventName); } // if (targets.indexOf(target) == -1) { this.targets.push(target); } gesture = this.get(target.id + '-' + name); if (!gesture) { gesture = this.create(Ext.apply({}, options || {}, { target: target, type: name })); gestures.push(gesture); Ext.Element.data(target, 'x-gestures', gestures); } gesture.addListener(eventName, listener); // If there is already a finger down, then instantly start the gesture if (this.startedChangedTouch && this.currentTargets.contains(target) && !gesture.started) { this.startGesture(gesture); if (gesture.listenForStart) { gesture.onTouchStart(this.startEvent, this.startedTouches[0]); } } }, removeEventListener: function(target, eventName, listener) { target = Ext.getDom(target); var name = this.getGestureName(eventName), gestures = Ext.Element.data(target, 'x-gestures') || [], gesture; gesture = this.get(target.id + '-' + name); if (gesture) { gesture.removeListener(eventName, listener); for (name in gesture.listeners) { return; } gesture.destroy(); gestures.remove(gesture); Ext.Element.data(target, 'x-gestures', gestures); } }, getGestureName: function(ename) { return this.names && this.names[ename]; }, registerType: function(type, cls) { var handles = cls.prototype.handles, i, ln; this.types[type] = cls; cls[this.typeName] = type; if (!handles) { handles = cls.prototype.handles = [type]; } this.names = this.names || {}; for (i = 0, ln = handles.length; i < ln; i++) { this.names[handles[i]] = type; } } }); Ext.regGesture = function() { return Ext.gesture.Manager.registerType.apply(Ext.gesture.Manager, arguments); };