[TemporaryStorage] add files required for SDK build
[samples/web/TemporaryStorage.git] / tizen-web-ui-fw / latest / js / src / jquery.mobile.vmouse.js
1
2 // This plugin is an experiment for abstracting away the touch and mouse
3 // events so that developers don't have to worry about which method of input
4 // the device their document is loaded on supports.
5 //
6 // The idea here is to allow the developer to register listeners for the
7 // basic mouse events, such as mousedown, mousemove, mouseup, and click,
8 // and the plugin will take care of registering the correct listeners
9 // behind the scenes to invoke the listener at the fastest possible time
10 // for that device, while still retaining the order of event firing in
11 // the traditional mouse environment, should multiple handlers be registered
12 // on the same element for different events.
13 //
14 // The current version exposes the following virtual events to jQuery bind methods:
15 // "vmouseover vmousedown vmousemove vmouseup vclick vmouseout vmousecancel"
16
17 (function( $, window, document, undefined ) {
18
19 var dataPropertyName = "virtualMouseBindings",
20         touchTargetPropertyName = "virtualTouchID",
21         virtualEventNames = "vmouseover vmousedown vmousemove vmouseup vclick vmouseout vmousecancel".split( " " ),
22         touchEventProps = "clientX clientY pageX pageY screenX screenY".split( " " ),
23         mouseHookProps = $.event.mouseHooks ? $.event.mouseHooks.props : [],
24         mouseEventProps = $.event.props.concat( mouseHookProps ),
25         activeDocHandlers = {},
26         resetTimerID = 0,
27         startX = 0,
28         startY = 0,
29         didScroll = false,
30         clickBlockList = [],
31         blockMouseTriggers = false,
32         blockTouchTriggers = false,
33         eventCaptureSupported = "addEventListener" in document,
34         $document = $.mobile.$document,
35         nextTouchID = 1,
36         lastTouchID = 0, threshold;
37
38 $.vmouse = {
39         moveDistanceThreshold: 10,
40         clickDistanceThreshold: 10,
41         resetTimerDuration: 1500
42 };
43
44 function getNativeEvent( event ) {
45
46         while ( event && typeof event.originalEvent !== "undefined" ) {
47                 event = event.originalEvent;
48         }
49         return event;
50 }
51
52 function createVirtualEvent( event, eventType ) {
53
54         var t = event.type,
55                 oe, props, ne, prop, ct, touch, i, j, len;
56
57         event = $.Event( event );
58         event.type = eventType;
59
60         oe = event.originalEvent;
61         props = $.event.props;
62
63         // addresses separation of $.event.props in to $.event.mouseHook.props and Issue 3280
64         // https://github.com/jquery/jquery-mobile/issues/3280
65         if ( t.search( /^(mouse|click)/ ) > -1 ) {
66                 props = mouseEventProps;
67         }
68
69         // copy original event properties over to the new event
70         // this would happen if we could call $.event.fix instead of $.Event
71         // but we don't have a way to force an event to be fixed multiple times
72         if ( oe ) {
73                 for ( i = props.length, prop; i; ) {
74                         prop = props[ --i ];
75                         event[ prop ] = oe[ prop ];
76                 }
77         }
78
79         // make sure that if the mouse and click virtual events are generated
80         // without a .which one is defined
81         if ( t.search(/mouse(down|up)|click/) > -1 && !event.which ) {
82                 event.which = 1;
83         }
84
85         if ( t.search(/^touch/) !== -1 ) {
86                 ne = getNativeEvent( oe );
87                 t = ne.touches;
88                 ct = ne.changedTouches;
89                 touch = ( t && t.length ) ? t[0] : ( ( ct && ct.length ) ? ct[ 0 ] : undefined );
90
91                 if ( touch ) {
92                         for ( j = 0, len = touchEventProps.length; j < len; j++) {
93                                 prop = touchEventProps[ j ];
94                                 event[ prop ] = touch[ prop ];
95                         }
96                 }
97         }
98
99         return event;
100 }
101
102 function getVirtualBindingFlags( element ) {
103
104         var flags = {},
105                 b, k;
106
107         while ( element ) {
108
109                 b = $.data( element, dataPropertyName );
110
111                 for (  k in b ) {
112                         if ( b[ k ] ) {
113                                 flags[ k ] = flags.hasVirtualBinding = true;
114                         }
115                 }
116                 element = element.parentNode;
117         }
118         return flags;
119 }
120
121 function getClosestElementWithVirtualBinding( element, eventType ) {
122         var b;
123         while ( element ) {
124
125                 b = $.data( element, dataPropertyName );
126
127                 if ( b && ( !eventType || b[ eventType ] ) ) {
128                         return element;
129                 }
130                 element = element.parentNode;
131         }
132         return null;
133 }
134
135 function enableTouchBindings() {
136         blockTouchTriggers = false;
137 }
138
139 function disableTouchBindings() {
140         blockTouchTriggers = true;
141 }
142
143 function enableMouseBindings() {
144         lastTouchID = 0;
145         clickBlockList.length = 0;
146         blockMouseTriggers = false;
147
148         // When mouse bindings are enabled, our
149         // touch bindings are disabled.
150         disableTouchBindings();
151 }
152
153 function disableMouseBindings() {
154         // When mouse bindings are disabled, our
155         // touch bindings are enabled.
156         enableTouchBindings();
157 }
158
159 function startResetTimer() {
160         clearResetTimer();
161         resetTimerID = setTimeout( function() {
162                 resetTimerID = 0;
163                 enableMouseBindings();
164         }, $.vmouse.resetTimerDuration );
165 }
166
167 function clearResetTimer() {
168         if ( resetTimerID ) {
169                 clearTimeout( resetTimerID );
170                 resetTimerID = 0;
171         }
172 }
173
174 function triggerVirtualEvent( eventType, event, flags ) {
175         var ve;
176
177         if ( ( flags && flags[ eventType ] ) ||
178                                 ( !flags && getClosestElementWithVirtualBinding( event.target, eventType ) ) ) {
179
180                 ve = createVirtualEvent( event, eventType );
181
182                 $( event.target).trigger( ve );
183         }
184
185         return ve;
186 }
187
188 function mouseEventCallback( event ) {
189         var touchID = $.data( event.target, touchTargetPropertyName );
190
191         if ( ( $.support.touch === true ) && ( touchID === undefined ) ) {
192                 return;
193         }
194
195         if ( !blockMouseTriggers && ( !lastTouchID || lastTouchID !== touchID ) ) {
196                 var ve = triggerVirtualEvent( "v" + event.type, event );
197                 if ( ve ) {
198                         if ( ve.isDefaultPrevented() ) {
199                                 event.preventDefault();
200                         }
201                         if ( ve.isPropagationStopped() ) {
202                                 event.stopPropagation();
203                         }
204                         if ( ve.isImmediatePropagationStopped() ) {
205                                 event.stopImmediatePropagation();
206                         }
207                 }
208         }
209 }
210
211 function handleTouchStart( event ) {
212
213         var touches = getNativeEvent( event ).touches,
214                 target, flags;
215
216         if ( touches && touches.length === 1 ) {
217
218                 target = event.target;
219                 flags = getVirtualBindingFlags( target );
220
221                 if ( flags.hasVirtualBinding ) {
222
223                         lastTouchID = nextTouchID++;
224                         $.data( target, touchTargetPropertyName, lastTouchID );
225
226                         clearResetTimer();
227
228                         disableMouseBindings();
229                         didScroll = false;
230
231                         var t = getNativeEvent( event ).touches[ 0 ];
232                         startX = t.pageX;
233                         startY = t.pageY;
234
235                         triggerVirtualEvent( "vmouseover", event, flags );
236                         triggerVirtualEvent( "vmousedown", event, flags );
237                 }
238         }
239 }
240
241 function handleScroll( event ) {
242         if ( blockTouchTriggers ) {
243                 return;
244         }
245
246         if ( !didScroll ) {
247                 triggerVirtualEvent( "vmousecancel", event, getVirtualBindingFlags( event.target ) );
248         }
249
250         didScroll = true;
251         startResetTimer();
252 }
253
254 function handleTouchMove( event ) {
255         if ( blockTouchTriggers ) {
256                 return;
257         }
258
259         var t = getNativeEvent( event ).touches[ 0 ],
260                 didCancel = didScroll,
261                 moveThreshold = $.vmouse.moveDistanceThreshold,
262                 flags = getVirtualBindingFlags( event.target );
263
264                 didScroll = didScroll ||
265                         ( Math.abs( t.pageX - startX ) > moveThreshold ||
266                                 Math.abs( t.pageY - startY ) > moveThreshold );
267
268
269         if ( didScroll && !didCancel ) {
270                 triggerVirtualEvent( "vmousecancel", event, flags );
271         }
272
273         triggerVirtualEvent( "vmousemove", event, flags );
274         startResetTimer();
275 }
276
277 function handleTouchEnd( event ) {
278         if ( blockTouchTriggers ) {
279                 return;
280         }
281
282         disableTouchBindings();
283
284         var flags = getVirtualBindingFlags( event.target ),
285                 t;
286         triggerVirtualEvent( "vmouseup", event, flags );
287
288         if ( !didScroll ) {
289                 var ve = triggerVirtualEvent( "vclick", event, flags );
290                 if ( ve && ve.isDefaultPrevented() ) {
291                         // The target of the mouse events that follow the touchend
292                         // event don't necessarily match the target used during the
293                         // touch. This means we need to rely on coordinates for blocking
294                         // any click that is generated.
295                         t = getNativeEvent( event ).changedTouches[ 0 ];
296                         clickBlockList.push({
297                                 touchID: lastTouchID,
298                                 target: event.target,
299                                 x: t.clientX,
300                                 y: t.clientY
301                         });
302
303                         // Prevent any mouse events that follow from triggering
304                         // virtual event notifications.
305                         blockMouseTriggers = true;
306                 }
307         }
308         triggerVirtualEvent( "vmouseout", event, flags);
309         didScroll = false;
310
311         startResetTimer();
312 }
313
314 function hasVirtualBindings( ele ) {
315         var bindings = $.data( ele, dataPropertyName ),
316                 k;
317
318         if ( bindings ) {
319                 for ( k in bindings ) {
320                         if ( bindings[ k ] ) {
321                                 return true;
322                         }
323                 }
324         }
325         return false;
326 }
327
328 function dummyMouseHandler() {}
329
330 function getSpecialEventObject( eventType ) {
331         var realType = eventType.substr( 1 );
332
333         return {
334                 setup: function( data, namespace ) {
335                         // If this is the first virtual mouse binding for this element,
336                         // add a bindings object to its data.
337
338                         if ( !hasVirtualBindings( this ) ) {
339                                 $.data( this, dataPropertyName, {} );
340                         }
341
342                         // If setup is called, we know it is the first binding for this
343                         // eventType, so initialize the count for the eventType to zero.
344                         var bindings = $.data( this, dataPropertyName );
345                         bindings[ eventType ] = true;
346
347                         // If this is the first virtual mouse event for this type,
348                         // register a global handler on the document.
349
350                         activeDocHandlers[ eventType ] = ( activeDocHandlers[ eventType ] || 0 ) + 1;
351
352                         if ( activeDocHandlers[ eventType ] === 1 ) {
353                                 $document.bind( realType, mouseEventCallback );
354                         }
355
356                         // Some browsers, like Opera Mini, won't dispatch mouse/click events
357                         // for elements unless they actually have handlers registered on them.
358                         // To get around this, we register dummy handlers on the elements.
359
360                         $( this ).bind( realType, dummyMouseHandler );
361
362                         // For now, if event capture is not supported, we rely on mouse handlers.
363                         if ( eventCaptureSupported ) {
364                                 // If this is the first virtual mouse binding for the document,
365                                 // register our touchstart handler on the document.
366
367                                 activeDocHandlers[ "touchstart" ] = ( activeDocHandlers[ "touchstart" ] || 0) + 1;
368
369                                 if ( activeDocHandlers[ "touchstart" ] === 1 ) {
370                                         $document.bind( "touchstart", handleTouchStart )
371                                                 .bind( "touchend", handleTouchEnd )
372
373                                                 // On touch platforms, touching the screen and then dragging your finger
374                                                 // causes the window content to scroll after some distance threshold is
375                                                 // exceeded. On these platforms, a scroll prevents a click event from being
376                                                 // dispatched, and on some platforms, even the touchend is suppressed. To
377                                                 // mimic the suppression of the click event, we need to watch for a scroll
378                                                 // event. Unfortunately, some platforms like iOS don't dispatch scroll
379                                                 // events until *AFTER* the user lifts their finger (touchend). This means
380                                                 // we need to watch both scroll and touchmove events to figure out whether
381                                                 // or not a scroll happenens before the touchend event is fired.
382
383                                                 .bind( "touchmove", handleTouchMove )
384                                                 .bind( "scroll", handleScroll );
385                                 }
386                         }
387                 },
388
389                 teardown: function( data, namespace ) {
390                         // If this is the last virtual binding for this eventType,
391                         // remove its global handler from the document.
392
393                         --activeDocHandlers[ eventType ];
394
395                         if ( !activeDocHandlers[ eventType ] ) {
396                                 $document.unbind( realType, mouseEventCallback );
397                         }
398
399                         if ( eventCaptureSupported ) {
400                                 // If this is the last virtual mouse binding in existence,
401                                 // remove our document touchstart listener.
402
403                                 --activeDocHandlers[ "touchstart" ];
404
405                                 if ( !activeDocHandlers[ "touchstart" ] ) {
406                                         $document.unbind( "touchstart", handleTouchStart )
407                                                 .unbind( "touchmove", handleTouchMove )
408                                                 .unbind( "touchend", handleTouchEnd )
409                                                 .unbind( "scroll", handleScroll );
410                                 }
411                         }
412
413                         var $this = $( this ),
414                                 bindings = $.data( this, dataPropertyName );
415
416                         // teardown may be called when an element was
417                         // removed from the DOM. If this is the case,
418                         // jQuery core may have already stripped the element
419                         // of any data bindings so we need to check it before
420                         // using it.
421                         if ( bindings ) {
422                                 bindings[ eventType ] = false;
423                         }
424
425                         // Unregister the dummy event handler.
426
427                         $this.unbind( realType, dummyMouseHandler );
428
429                         // If this is the last virtual mouse binding on the
430                         // element, remove the binding data from the element.
431
432                         if ( !hasVirtualBindings( this ) ) {
433                                 $this.removeData( dataPropertyName );
434                         }
435                 }
436         };
437 }
438
439 // Expose our custom events to the jQuery bind/unbind mechanism.
440
441 for ( var i = 0; i < virtualEventNames.length; i++ ) {
442         $.event.special[ virtualEventNames[ i ] ] = getSpecialEventObject( virtualEventNames[ i ] );
443 }
444
445 // Add a capture click handler to block clicks.
446 // Note that we require event capture support for this so if the device
447 // doesn't support it, we punt for now and rely solely on mouse events.
448 if ( eventCaptureSupported ) {
449         document.addEventListener( "click", function( e ) {
450                 var cnt = clickBlockList.length,
451                         target = e.target,
452                         x, y, ele, i, o, touchID;
453
454                 if ( cnt ) {
455                         x = e.clientX;
456                         y = e.clientY;
457                         threshold = $.vmouse.clickDistanceThreshold;
458
459                         // The idea here is to run through the clickBlockList to see if
460                         // the current click event is in the proximity of one of our
461                         // vclick events that had preventDefault() called on it. If we find
462                         // one, then we block the click.
463                         //
464                         // Why do we have to rely on proximity?
465                         //
466                         // Because the target of the touch event that triggered the vclick
467                         // can be different from the target of the click event synthesized
468                         // by the browser. The target of a mouse/click event that is syntehsized
469                         // from a touch event seems to be implementation specific. For example,
470                         // some browsers will fire mouse/click events for a link that is near
471                         // a touch event, even though the target of the touchstart/touchend event
472                         // says the user touched outside the link. Also, it seems that with most
473                         // browsers, the target of the mouse/click event is not calculated until the
474                         // time it is dispatched, so if you replace an element that you touched
475                         // with another element, the target of the mouse/click will be the new
476                         // element underneath that point.
477                         //
478                         // Aside from proximity, we also check to see if the target and any
479                         // of its ancestors were the ones that blocked a click. This is necessary
480                         // because of the strange mouse/click target calculation done in the
481                         // Android 2.1 browser, where if you click on an element, and there is a
482                         // mouse/click handler on one of its ancestors, the target will be the
483                         // innermost child of the touched element, even if that child is no where
484                         // near the point of touch.
485
486                         ele = target;
487
488                         while ( ele ) {
489                                 for ( i = 0; i < cnt; i++ ) {
490                                         o = clickBlockList[ i ];
491                                         touchID = 0;
492
493                                         if ( ( ele === target && target === o.target && Math.abs( o.x - x ) < threshold && Math.abs( o.y - y ) < threshold ) ||
494                                                                 $.data( ele, touchTargetPropertyName ) === o.touchID ) {
495                                                 // XXX: We may want to consider removing matches from the block list
496                                                 //      instead of waiting for the reset timer to fire.
497                                                 e.preventDefault();
498                                                 e.stopPropagation();
499                                                 return;
500                                         }
501                                 }
502                                 ele = ele.parentNode;
503                         }
504                 }
505         }, true);
506 }
507 })( jQuery, window, document );