]>
git.proxmox.com Git - sencha-touch.git/blob - src/src/event/publisher/TouchGesture.js
4 Ext
.define('Ext.event.publisher.TouchGesture', {
6 extend
: 'Ext.event.publisher.Dom',
14 isNotPreventable
: /^(select|a)$/i,
16 handledEvents
: ['touchstart', 'touchmove', 'touchend', 'touchcancel'],
19 mousedown
: 'touchstart',
20 mousemove
: 'touchmove',
31 constructor: function(config
) {
34 this.eventProcessors
= {
35 touchstart
: this.onTouchStart
,
36 touchmove
: this.onTouchMove
,
37 touchend
: this.onTouchEnd
,
38 touchcancel
: this.onTouchEnd
41 this.eventToRecognizerMap
= {};
43 this.activeRecognizers
= [];
47 this.currentIdentifiers
= [];
49 if (Ext
.browser
.is
.Chrome
&& Ext
.os
.is
.Android
) {
50 this.screenPositionRatio
= Ext
.browser
.version
.gt('18') ? 1 : 1 / window
.devicePixelRatio
;
52 else if (Ext
.browser
.is
.AndroidStock4
) {
53 this.screenPositionRatio
= 1;
55 else if (Ext
.os
.is
.BlackBerry
) {
56 this.screenPositionRatio
= 1 / window
.devicePixelRatio
;
58 else if (Ext
.browser
.engineName
== 'WebKit' && Ext
.os
.is
.Desktop
) {
59 this.screenPositionRatio
= 1;
62 this.screenPositionRatio
= window
.innerWidth
/ window
.screen
.width
;
64 this.initConfig(config
);
66 if (Ext
.feature
.has
.Touch
) {
67 // bind handlers that are only invoked when the browser has touchevents
68 me
.onTargetTouchMove
= me
.onTargetTouchMove
.bind(me
);
69 me
.onTargetTouchEnd
= me
.onTargetTouchEnd
.bind(me
);
72 return this.callSuper();
75 applyRecognizers: function(recognizers
) {
78 for (i
in recognizers
) {
79 if (recognizers
.hasOwnProperty(i
)) {
80 recognizer
= recognizers
[i
];
83 this.registerRecognizer(recognizer
);
91 handles: function(eventName
) {
92 return this.callSuper(arguments
) || this.eventToRecognizerMap
.hasOwnProperty(eventName
);
95 doesEventBubble: function() {
96 // All touch events bubble
99 onEvent: function(e
) {
101 lastEventType
= this.lastEventType
,
104 if (this.eventProcessors
[type
]) {
105 this.eventProcessors
[type
].call(this, e
);
109 if ('button' in e
&& e
.button
> 0) {
113 // Temporary fix for a recent Chrome bugs where events don't seem to bubble up to document
114 // when the element is being animated with webkit-transition (2 mousedowns without any mouseup)
115 if (type
=== 'mousedown' && lastEventType
&& lastEventType
!== 'mouseup') {
116 var fixedEvent
= document
.createEvent("MouseEvent");
117 fixedEvent
.initMouseEvent('mouseup', e
.bubbles
, e
.cancelable
,
118 document
.defaultView
, e
.detail
, e
.screenX
, e
.screenY
, e
.clientX
,
119 e
.clientY
, e
.ctrlKey
, e
.altKey
, e
.shiftKey
, e
.metaKey
, e
.metaKey
,
120 e
.button
, e
.relatedTarget
);
122 this.onEvent(fixedEvent
);
125 if (type
!== 'mousemove') {
126 this.lastEventType
= type
;
130 e
.touches
= (type
!== 'mouseup') ? touchList
: [];
131 e
.targetTouches
= (type
!== 'mouseup') ? touchList
: [];
132 e
.changedTouches
= touchList
;
134 this.eventProcessors
[this.mouseToTouchMap
[type
]].call(this, e
);
138 registerRecognizer: function(recognizer
) {
139 var map
= this.eventToRecognizerMap
,
140 activeRecognizers
= this.activeRecognizers
,
141 handledEvents
= recognizer
.getHandledEvents(),
144 recognizer
.setOnRecognized(this.onRecognized
);
145 recognizer
.setCallbackScope(this);
147 for (i
= 0,ln
= handledEvents
.length
; i
< ln
; i
++) {
148 eventName
= handledEvents
[i
];
150 map
[eventName
] = recognizer
;
153 activeRecognizers
.push(recognizer
);
158 onRecognized: function(eventName
, e
, touches
, info
) {
159 var targetGroups
= [],
164 return this.publish(eventName
, touches
[0].targets
, e
, info
);
167 for (i
= 0; i
< ln
; i
++) {
169 targetGroups
.push(touch
.targets
);
172 targets
= this.getCommonTargets(targetGroups
);
174 this.publish(eventName
, targets
, e
, info
);
177 publish: function(eventName
, targets
, event
, info
) {
179 return this.callSuper([eventName
, targets
, event
]);
182 getCommonTargets: function(targetGroups
) {
183 var firstTargetGroup
= targetGroups
[0],
184 ln
= targetGroups
.length
;
187 return firstTargetGroup
;
190 var commonTargets
= [],
195 target
= firstTargetGroup
[firstTargetGroup
.length
- i
];
198 return commonTargets
;
201 for (j
= 1; j
< ln
; j
++) {
202 targets
= targetGroups
[j
];
204 if (targets
[targets
.length
- i
] !== target
) {
205 return commonTargets
;
209 commonTargets
.unshift(target
);
213 return commonTargets
;
216 invokeRecognizers: function(methodName
, e
) {
217 var recognizers
= this.activeRecognizers
,
218 ln
= recognizers
.length
,
221 if (methodName
=== 'onStart') {
222 for (i
= 0; i
< ln
; i
++) {
223 recognizers
[i
].isActive
= true;
227 for (i
= 0; i
< ln
; i
++) {
228 recognizer
= recognizers
[i
];
229 if (recognizer
.isActive
&& recognizer
[methodName
].call(recognizer
, e
) === false) {
230 recognizer
.isActive
= false;
235 getActiveRecognizers: function() {
236 return this.activeRecognizers
;
239 updateTouch: function(touch
) {
240 var identifier
= touch
.identifier
,
241 currentTouch
= this.touchesMap
[identifier
],
245 target
= this.getElementTarget(touch
.target
);
247 this.touchesMap
[identifier
] = currentTouch
= {
248 identifier
: identifier
,
250 targets
: this.getBubblingTargets(target
)
253 this.currentIdentifiers
.push(identifier
);
259 if (x
=== currentTouch
.pageX
&& y
=== currentTouch
.pageY
) {
263 currentTouch
.pageX
= x
;
264 currentTouch
.pageY
= y
;
265 currentTouch
.timeStamp
= touch
.timeStamp
;
266 currentTouch
.point
= new Ext
.util
.Point(x
, y
);
271 updateTouches: function(touches
) {
275 for (i
= 0, ln
= touches
.length
; i
< ln
; i
++) {
276 touch
= this.updateTouch(touches
[i
]);
278 changedTouches
.push(touch
);
282 return changedTouches
;
285 syncTouches: function (touches
) {
286 var touchIDs
= [], len
= touches
.length
,
287 i
, id
, touch
, ghostTouches
;
289 // Collect the actual touch IDs that exist
290 for (i
= 0; i
< len
; i
++) {
292 touchIDs
.push(touch
.identifier
);
295 // Compare actual IDs to cached IDs
296 // Remove any that are not real anymore
297 ghostTouches
= Ext
.Array
.difference(this.currentIdentifiers
, touchIDs
);
298 len
= ghostTouches
.length
;
300 for (i
= 0; i
< len
; i
++) {
301 id
= ghostTouches
[i
];
302 Ext
.Array
.remove(this.currentIdentifiers
, id
);
303 delete this.touchesMap
[id
];
307 factoryEvent: function(e
) {
308 return new Ext
.event
.Touch(e
, null, this.touchesMap
, this.currentIdentifiers
);
311 onTouchStart: function(e
) {
312 var changedTouches
= e
.changedTouches
,
315 ln
= changedTouches
.length
,
316 isNotPreventable
= this.isNotPreventable
,
317 isTouch
= (e
.type
=== 'touchstart'),
321 // Potentially sync issue from various reasons.
322 // example: ios8 does not dispatch touchend on audio element play/pause tap.
323 if (touches
&& touches
.length
< this.currentIdentifiers
.length
+ 1) {
324 this.syncTouches(touches
);
327 this.updateTouches(changedTouches
);
329 e
= this.factoryEvent(e
);
330 changedTouches
= e
.changedTouches
;
333 // Android event system will not dispatch touchend for any multitouch
334 // event that has not been preventDefaulted.
335 if(Ext
.browser
.is
.AndroidStock
&& this.currentIdentifiers
.length
>= 2) {
339 // If targets are destroyed while touches are active on them
340 // we need these listeners to sync up our internal TouchesMap
342 target
.addEventListener('touchmove', me
.onTargetTouchMove
);
343 target
.addEventListener('touchend', me
.onTargetTouchEnd
);
344 target
.addEventListener('touchcancel', me
.onTargetTouchEnd
);
347 for (i
= 0; i
< ln
; i
++) {
348 touch
= changedTouches
[i
];
349 this.publish('touchstart', touch
.targets
, e
, {touch
: touch
});
352 if (!this.isStarted
) {
353 this.isStarted
= true;
354 this.invokeRecognizers('onStart', e
);
357 this.invokeRecognizers('onTouchStart', e
);
359 parent
= target
.parentNode
|| {};
362 onTouchMove: function(e
) {
363 if (!this.isStarted
) {
367 if (!this.animationQueued
) {
368 this.animationQueued
= true;
369 Ext
.AnimationQueue
.start('onAnimationFrame', this);
372 this.lastMoveEvent
= e
;
375 onAnimationFrame: function() {
376 var event
= this.lastMoveEvent
;
379 this.lastMoveEvent
= null;
380 this.doTouchMove(event
);
384 doTouchMove: function(e
) {
385 var changedTouches
, i
, ln
, touch
;
387 changedTouches
= this.updateTouches(e
.changedTouches
);
389 ln
= changedTouches
.length
;
391 e
= this.factoryEvent(e
);
393 for (i
= 0; i
< ln
; i
++) {
394 touch
= changedTouches
[i
];
395 this.publish('touchmove', touch
.targets
, e
, {touch
: touch
});
399 this.invokeRecognizers('onTouchMove', e
);
403 onTouchEnd: function(e
) {
404 if (!this.isStarted
) {
408 if (this.lastMoveEvent
) {
409 this.onAnimationFrame();
412 var touchesMap
= this.touchesMap
,
413 currentIdentifiers
= this.currentIdentifiers
,
414 changedTouches
= e
.changedTouches
,
415 ln
= changedTouches
.length
,
416 identifier
, i
, touch
;
418 this.updateTouches(changedTouches
);
420 changedTouches
= e
.changedTouches
;
422 for (i
= 0; i
< ln
; i
++) {
423 Ext
.Array
.remove(currentIdentifiers
, changedTouches
[i
].identifier
);
426 e
= this.factoryEvent(e
);
428 for (i
= 0; i
< ln
; i
++) {
429 identifier
= changedTouches
[i
].identifier
;
430 touch
= touchesMap
[identifier
];
431 delete touchesMap
[identifier
];
432 this.publish('touchend', touch
.targets
, e
, {touch
: touch
});
435 this.invokeRecognizers('onTouchEnd', e
);
437 // This previously was set to e.touches.length === 1 to catch errors in syncing
438 // this has since been addressed to keep proper sync and now this is a catch for
439 // a sync error in touches to reset our internal maps
440 if (e
.touches
&& e
.touches
.length
=== 0 && currentIdentifiers
.length
) {
441 currentIdentifiers
.length
= 0;
442 this.touchesMap
= {};
445 if (currentIdentifiers
.length
=== 0) {
446 this.isStarted
= false;
447 this.invokeRecognizers('onEnd', e
);
448 if (this.animationQueued
) {
449 this.animationQueued
= false;
450 Ext
.AnimationQueue
.stop('onAnimationFrame', this);
455 onTargetTouchMove: function(e
) {
456 if (!Ext
.getBody().contains(e
.target
)) {
461 onTargetTouchEnd: function(e
) {
467 // Determine how many active touches there are on this target
468 for (identifier
in this.touchesMap
) {
469 touchTarget
= this.touchesMap
[identifier
].target
;
470 if (touchTarget
=== target
) {
475 // If this is the last active touch on the target remove the target listeners
476 if (touchCount
<= 1) {
477 target
.removeEventListener('touchmove', me
.onTargetTouchMove
);
478 target
.removeEventListener('touchend', me
.onTargetTouchEnd
);
479 target
.removeEventListener('touchcancel', me
.onTargetTouchEnd
);
482 if (!Ext
.getBody().contains(target
)) {
488 if (Ext
.feature
.has
.Pointer
) {
491 MSPointerDown
: 'touchstart',
492 MSPointerMove
: 'touchmove',
493 MSPointerUp
: 'touchend',
494 MSPointerCancel
: 'touchcancel',
495 pointerdown
: 'touchstart',
496 pointermove
: 'touchmove',
497 pointerup
: 'touchend',
498 pointercancel
: 'touchcancel'
502 touchstart
: 'MSPointerDown',
503 touchmove
: 'MSPointerMove',
504 touchend
: 'MSPointerUp',
505 touchcancel
: 'MSPointerCancel'
508 attachListener: function(eventName
, doc
) {
509 eventName
= this.touchToPointerMap
[eventName
];
515 return this.callOverridden([eventName
, doc
]);
518 onEvent: function(e
) {
521 this.currentIdentifiers
.length
=== 0 &&
522 // This is for IE 10 and IE 11
523 (e
.pointerType
=== e
.MSPOINTER_TYPE_TOUCH
|| e
.pointerType
=== "touch") &&
524 // This is for IE 10 and IE 11
525 (type
=== "MSPointerMove" || type
=== "pointermove")
527 type
= "MSPointerDown";
530 if ('button' in e
&& e
.button
> 0) {
534 type
= this.pointerToTouchMap
[type
];
535 e
.identifier
= e
.pointerId
;
536 e
.changedTouches
= [e
];
538 this.eventProcessors
[type
].call(this, e
);
542 else if (!Ext
.browser
.is
.Ripple
&& (Ext
.os
.is
.ChromeOS
|| !Ext
.feature
.has
.Touch
)) {
544 handledEvents
: ['touchstart', 'touchmove', 'touchend', 'touchcancel', 'mousedown', 'mousemove', 'mouseup']