Propagate root prefix into JaaScript environment
[infodrom.org/service.infodrom.org] / src / jquery.event.ue.js
1 /*
2  * Jquery plugin for unified mouse and touch events
3  *
4  * Copyright (c) 2013 Michael S. Mikowski
5  * (mike[dot]mikowski[at]gmail[dotcom])
6  *
7  * Dual licensed under the MIT or GPL Version 2
8  * http://jquery.org/license
9  *
10  * Versions
11  *  0.3.0 - Initial jQuery plugin site release
12  *        - Replaced scrollwheel zoom with drag motion.
13  *          This resolved a conflict with scrollable areas.
14  *  0.3.1 - Change for jQuery plugins site
15  *  0.3.2 - Updated to jQuery 1.9.1.
16  *          Confirmed 1.7.0-1.9.1 compatibility.
17  *  0.4.2 - Updated documentation
18  *  0.4.3 - Removed fatal execption possibility if originalEvent
19  *          is not defined on event object
20  *
21 */
22
23 /*jslint           browser : true,   continue : true,
24   devel  : true,    indent : 2,       maxerr  : 50,
25   newcap : true,  plusplus : true,    regexp  : true,
26   sloppy : true,      vars : true,     white  : true
27 */
28 /*global jQuery, sl */
29
30 (function ( $ ) {
31   //---------------- BEGIN MODULE SCOPE VARIABLES --------------
32   var
33     $Special        = $.event.special,  // shortcut for special event
34     motionMapMap    = {},         // map of pointer motions by cursor
35     isMoveBound     = false,      // flag if move handlers bound
36     pxPinchZoom     = -1,         // distance between pinch-zoom points
37     optionKey       = 'ue_bound', // data key for storing options
38     doDisableMouse  = false,      // flag to discard mouse input
39     defaultOptMap   = {           // Default option hash
40       bound_ns_map  : {},         // namspace hash e.g. bound_ns_map.utap.fred
41       wheel_ratio   : 15,         // multiplier for mousewheel delta
42       px_radius     : 3,          // 'distance' dragged before dragstart
43       ignore_class  : ':input',   // 'not' suppress matching elements
44       tap_time      : 200,        // millisecond max time to consider tap
45       held_tap_time : 300         // millisecond min time to consider taphold
46     },
47     callbackList  = [],           // global callback stack
48     zoomMouseNum  = 1,            // multiplier for mouse zoom
49     zoomTouchNum  = 4,            // multiplier for touch zoom
50
51     boundList, Ue,
52     motionDragId,  motionHeldId, motionDzoomId,
53     motion1ZoomId, motion2ZoomId,
54
55     checkMatchVal, removeListVal,  pushUniqVal,   makeListPlus,
56     fnHeld,        fnMotionStart,  fnMotionMove,
57     fnMotionEnd,   onMouse,        onTouch,
58     onMousewheel
59     ;
60   //----------------- END MODULE SCOPE VARIABLES ---------------
61
62   //------------------- BEGIN UTILITY METHODS ------------------
63   // Begin utiltity /makeListPlus/
64   // Returns an array with much desired methods:
65   //   * remove_val(value) : remove element that matches
66   //     the provided value. Returns number of elements
67   //     removed.
68   //   * match_val(value)  : shows if a value exists
69   //   * push_uniq(value)  : pushes a value onto the stack
70   //     iff it does not already exist there
71   // Note: the reason I need this is to compare objects to
72   //   objects (perhaps jQuery has something similar?)
73   checkMatchVal = function ( data ) {
74     var match_count = 0, idx;
75     for ( idx = this.length; idx; 0 ) {
76       if ( this[--idx] === data ) { match_count++; }
77     }
78     return match_count;
79   };
80   removeListVal = function ( data ) {
81     var removed_count = 0, idx;
82     for ( idx = this.length; idx; 0 ) {
83       if ( this[--idx] === data ) {
84         this.splice(idx, 1);
85         removed_count++;
86         idx++;
87       }
88     }
89     return removed_count;
90   };
91   pushUniqVal = function ( data ) {
92     if ( checkMatchVal.call(this, data ) ) { return false; }
93     this.push( data );
94     return true;
95   };
96   // primary utility
97   makeListPlus = function ( input_list ) {
98     if ( input_list && $.isArray(input_list) ) {
99       if ( input_list.remove_val ) {
100         console.warn( 'The array appears to already have listPlus capabilities' );
101         return input_list;
102       }
103     }
104     else {
105       input_list = [];
106     }
107     input_list.remove_val = removeListVal;
108     input_list.match_val  = checkMatchVal;
109     input_list.push_uniq  = pushUniqVal;
110
111     return input_list;
112   };
113   // End utility /makeListPlus/
114   //-------------------- END UTILITY METHODS -------------------
115
116   //--------------- BEGIN JQUERY SPECIAL EVENTS ----------------
117   // Unique array for bound objects
118   boundList = makeListPlus();
119
120   // Begin define special event handlers
121   Ue = {
122     setup : function( data, a_names, fn_bind ) {
123       var
124         this_el     = this,
125         $to_bind    = $(this_el),
126         seen_map    = {},
127         option_map, idx, namespace_key, ue_namespace_code, namespace_list
128         ;
129
130       // if previous related event bound do not rebind, but do add to
131       // type of event bound to this element, if not already noted
132       if ( $.data( this, optionKey ) ) { return; }
133
134       option_map = {};
135       $.extend( true, option_map, defaultOptMap );
136       $.data( this_el, optionKey, option_map );
137
138       namespace_list = makeListPlus(a_names.slice(0));
139       if ( ! namespace_list.length 
140         || namespace_list[0] === ""
141       ) { namespace_list = ["000"]; }
142
143       NSPACE_00:
144       for ( idx = 0; idx < namespace_list.length; idx++ ) {
145         namespace_key = namespace_list[idx];
146
147         if ( ! namespace_key ) { continue NSPACE_00; }
148         if ( seen_map.hasOwnProperty(namespace_key) ) { continue NSPACE_00; }
149
150         seen_map[namespace_key] = true;
151
152         ue_namespace_code = '.__ue' + namespace_key;
153
154         $to_bind.bind( 'mousedown'  + ue_namespace_code, onMouse  );
155         $to_bind.bind( 'touchstart' + ue_namespace_code, onTouch );
156         $to_bind.bind( 'mousewheel' + ue_namespace_code, onMousewheel );
157       }
158
159       boundList.push_uniq( this_el ); // record as bound element
160
161       if ( ! isMoveBound ) {
162         // console.log('first element bound - adding global binds');
163         $(document).bind( 'mousemove.__ue',   onMouse );
164         $(document).bind( 'touchmove.__ue',   onTouch );
165         $(document).bind( 'mouseup.__ue'  ,   onMouse );
166         $(document).bind( 'touchend.__ue' ,   onTouch );
167         $(document).bind( 'touchcancel.__ue', onTouch );
168         isMoveBound = true;
169       }
170     },
171
172     // arg_map.type = string - name of event to bind
173     // arg_map.data = poly - whatever (optional) data was passed when binding
174     // arg_map.namespace = string - A sorted, dot-delimited list of namespaces
175     //   specified when binding the event
176     // arg_map.handler  = fn - the event handler the developer wishes to be bound
177     //   to the event.  This function should be called whenever the event
178     //   is triggered
179     // arg_map.guid = number - unique ID for event handler, provided by jQuery
180     // arg_map.selector = string - selector used by 'delegate' or 'live' jQuery
181     //   methods.  Only available when these methods are used.
182     //
183     // this - the element to which the event handler is being bound
184     // this always executes immediate after setup (if first binding)
185     add : function ( arg_map ) {
186       var
187         this_el         = this,
188         option_map      = $.data( this_el, optionKey ),
189         namespace_str   = arg_map.namespace,
190         event_type      = arg_map.type,
191         bound_ns_map, namespace_list, idx, namespace_key
192         ;
193       if ( ! option_map ) { return; }
194
195       bound_ns_map  = option_map.bound_ns_map;
196
197       if ( ! bound_ns_map[event_type] ) {
198         // this indicates a non-namespaced entry
199         bound_ns_map[event_type] = {};
200       }
201
202       if ( ! namespace_str ) { return; }
203
204       namespace_list = namespace_str.split('.');
205
206       for ( idx = 0; idx < namespace_list.length; idx++ ) {
207         namespace_key = namespace_list[idx];
208         bound_ns_map[event_type][namespace_key] = true;
209       }
210     },
211
212     remove : function ( arg_map ) {
213       var
214         elem_bound     = this,
215         option_map     = $.data( elem_bound, optionKey ),
216         bound_ns_map   = option_map.bound_ns_map,
217         event_type     = arg_map.type,
218         namespace_str  = arg_map.namespace,
219         namespace_list, idx, namespace_key
220         ;
221
222       if ( ! bound_ns_map[event_type] ) { return; }
223
224       // No namespace(s) provided:
225       // Remove complete record for custom event type (e.g. utap)
226       if ( ! namespace_str ) {
227         delete bound_ns_map[event_type];
228         return;
229       }
230
231       // Namespace(s) provided:
232       // Remove namespace flags from each custom event typei (e.g. utap)
233       // record.  If all claimed namespaces are removed, remove
234       // complete record.
235       namespace_list = namespace_str.split('.');
236
237       for ( idx = 0; idx < namespace_list.length; idx++ ) {
238         namespace_key = namespace_list[idx];
239         if (bound_ns_map[event_type][namespace_key]) {
240           delete bound_ns_map[event_type][namespace_key];
241         }
242       }
243
244       if ( $.isEmptyObject( bound_ns_map[event_type] ) ) {
245         delete bound_ns_map[event_type];
246       }
247     },
248
249     teardown : function( a_names ) {
250       var
251         elem_bound   = this,
252         $bound       = $(elem_bound),
253         option_map   = $.data( elem_bound, optionKey ),
254         bound_ns_map = option_map.bound_ns_map,
255         idx, namespace_key, ue_namespace_code, namespace_list
256         ;
257
258       // do not tear down if related handlers are still bound
259       if ( ! $.isEmptyObject( bound_ns_map ) ) { return; }
260
261       namespace_list = makeListPlus(a_names);
262       namespace_list.push_uniq('000');
263
264       NSPACE_01:
265       for ( idx = 0; idx < namespace_list.length; idx++ ) {
266         namespace_key = namespace_list[idx];
267
268         if ( ! namespace_key ) { continue NSPACE_01; }
269
270         ue_namespace_code = '.__ue' + namespace_key;
271         $bound.unbind( 'mousedown'  + ue_namespace_code );
272         $bound.unbind( 'touchstart' + ue_namespace_code );
273         $bound.unbind( 'mousewheel' + ue_namespace_code );
274       }
275
276       $.removeData( elem_bound, optionKey );
277
278       // Unbind document events only after last element element is removed
279       boundList.remove_val(this);
280       if ( boundList.length === 0 ) {
281         // console.log('last bound element removed - removing global binds');
282         $(document).unbind( 'mousemove.__ue');
283         $(document).unbind( 'touchmove.__ue');
284         $(document).unbind( 'mouseup.__ue');
285         $(document).unbind( 'touchend.__ue');
286         $(document).unbind( 'touchcancel.__ue');
287         isMoveBound = false;
288       }
289     }
290   };
291   // End define special event handlers
292   //--------------- BEGIN JQUERY SPECIAL EVENTS ----------------
293
294   //------------------ BEGIN MOTION CONTROLS -------------------
295   // Begin motion control /fnHeld/
296   fnHeld = function ( arg_map ) {
297     var
298       timestamp         = +new Date(),
299       motion_id    = arg_map.motion_id,
300       motion_map   = arg_map.motion_map,
301       bound_ns_map = arg_map.bound_ns_map,
302       event_ue
303       ;
304
305     delete motion_map.idto_tapheld;
306
307     if ( ! motion_map.do_allow_tap ) { return; }
308
309     motion_map.px_end_x     = motion_map.px_start_x;
310     motion_map.px_end_y     = motion_map.px_start_y;
311     motion_map.ms_timestop  = timestamp;
312     motion_map.ms_elapsed   = timestamp - motion_map.ms_timestart;
313
314     if ( bound_ns_map.uheld ) {
315       event_ue     = $.Event('uheld');
316       $.extend( event_ue, motion_map );
317       $(motion_map.elem_bound).trigger(event_ue);
318     }
319
320     // remove tracking, as we want no futher action on this motion
321     if ( bound_ns_map.uheldstart ) {
322       event_ue     = $.Event('uheldstart');
323       $.extend( event_ue, motion_map );
324       $(motion_map.elem_bound).trigger(event_ue);
325       motionHeldId = motion_id;
326     }
327     else {
328       delete motionMapMap[motion_id];
329     }
330   };
331   // End motion control /fnHeld/
332
333
334   // Begin motion control /fnMotionStart/
335   fnMotionStart = function ( arg_map ) {
336     var
337       motion_id      = arg_map.motion_id,
338       event_src      = arg_map.event_src,
339       request_dzoom  = arg_map.request_dzoom,
340
341       option_map     = $.data( arg_map.elem, optionKey ),
342       bound_ns_map   = option_map.bound_ns_map,
343       $target        = $(event_src.target ),
344       do_zoomstart   = false,
345       motion_map, cb_map, do_allow_tap, event_ue
346       ;
347
348     // this should never happen, but it does
349     if ( motionMapMap[ motion_id ] ) { return; }
350
351     if ( request_dzoom && ! bound_ns_map.uzoomstart ) { return; }
352
353     // :input selector includes text areas
354     if ( $target.is( option_map.ignore_class ) ) { return; }
355
356     do_allow_tap = bound_ns_map.utap
357       || bound_ns_map.uheld || bound_ns_map.uheldstart
358       ? true : false;
359
360     cb_map = callbackList.pop();
361
362     while ( cb_map ) {
363       if ( $target.is( cb_map.selector_str )
364         || $( arg_map.elem ).is( cb_map.selector_str )
365       ) {
366         if ( cb_map.callback_match ) {
367           cb_map.callback_match( arg_map );
368         }
369       }
370       else {
371         if ( cb_map.callback_nomatch ) {
372           cb_map.callback_nomatch( arg_map );
373         }
374       }
375       cb_map = callbackList.pop();
376     }
377
378     motion_map = {
379       do_allow_tap : do_allow_tap,
380       elem_bound   : arg_map.elem,
381       elem_target  : event_src.target,
382       ms_elapsed   : 0,
383       ms_timestart : event_src.timeStamp,
384       ms_timestop  : undefined,
385       option_map   : option_map,
386       orig_target  : event_src.target,
387       px_current_x : event_src.clientX,
388       px_current_y : event_src.clientY,
389       px_end_x     : undefined,
390       px_end_y     : undefined,
391       px_start_x   : event_src.clientX,
392       px_start_y   : event_src.clientY,
393       timeStamp    : event_src.timeStamp
394     };
395
396     motionMapMap[ motion_id ] = motion_map;
397
398     if ( bound_ns_map.uzoomstart ) {
399       if ( request_dzoom ) {
400         motionDzoomId = motion_id;
401       }
402       else if ( ! motion1ZoomId ) {
403         motion1ZoomId = motion_id;
404       }
405       else if ( ! motion2ZoomId ) {
406         motion2ZoomId = motion_id;
407         event_ue = $.Event('uzoomstart');
408         do_zoomstart = true;
409       }
410
411       if ( do_zoomstart ) {
412         event_ue = $.Event( 'uzoomstart' );
413         motion_map.px_delta_zoom = 0;
414         $.extend( event_ue, motion_map );
415         $(motion_map.elem_bound).trigger(event_ue);
416         return;
417       }
418     }
419
420     if ( bound_ns_map.uheld || bound_ns_map.uheldstart ) {
421       motion_map.idto_tapheld = setTimeout(
422         function() {
423           fnHeld({
424             motion_id  : motion_id,
425             motion_map   : motion_map,
426             bound_ns_map : bound_ns_map
427           });
428         },
429         option_map.held_tap_time
430       );
431     }
432   };
433   // End motion control /fnMotionStart/
434
435   // Begin motion control /fnMotionMove/
436   fnMotionMove  = function ( arg_map ) {
437     var
438       motion_id   = arg_map.motion_id,
439       event_src   = arg_map.event_src,
440       do_zoommove = false,
441       motion_map, option_map, bound_ns_map,
442       event_ue, px_pinch_zoom, px_delta_zoom,
443       mzoom1_map, mzoom2_map
444       ;
445
446     if ( ! motionMapMap[motion_id] ) { return; }
447
448     motion_map   = motionMapMap[motion_id];
449     option_map   = motion_map.option_map;
450     bound_ns_map = option_map.bound_ns_map;
451
452     motion_map.timeStamp    = event_src.timeStamp;
453     motion_map.elem_target  = event_src.target;
454     motion_map.ms_elapsed   = event_src.timeStamp - motion_map.ms_timestart;
455
456     motion_map.px_delta_x   = event_src.clientX - motion_map.px_current_x;
457     motion_map.px_delta_y   = event_src.clientY - motion_map.px_current_y;
458
459     motion_map.px_current_x = event_src.clientX;
460     motion_map.px_current_y = event_src.clientY;
461
462     // native event object override
463     motion_map.timeStamp    = event_src.timeStamp;
464
465     // disallow tap if outside of zone or time elapsed
466     // we use this for other events, so we do it every time
467     if ( motion_map.do_allow_tap ) {
468       if ( Math.abs(motion_map.px_delta_x) > option_map.px_radius
469         || Math.abs(motion_map.pd_delta_y) > option_map.px_radius
470         || motion_map.ms_elapsed           > option_map.tap_time
471       ) { motion_map.do_allow_tap = false; }
472     }
473
474     if ( motion1ZoomId && motion2ZoomId
475       && ( motion_id === motion1ZoomId
476         || motion_id === motion2ZoomId
477     )) {
478       motionMapMap[motion_id] = motion_map;
479       mzoom1_map = motionMapMap[motion1ZoomId];
480       mzoom2_map = motionMapMap[motion2ZoomId];
481
482       px_pinch_zoom = Math.floor(
483         Math.sqrt(
484             Math.pow((mzoom1_map.px_current_x - mzoom2_map.px_current_x),2)
485           + Math.pow((mzoom1_map.px_current_y - mzoom2_map.px_current_y),2)
486         ) +0.5
487       );
488
489       if ( pxPinchZoom === -1 ) { px_delta_zoom = 0; }
490       else { px_delta_zoom = ( px_pinch_zoom - pxPinchZoom ) * zoomTouchNum;}
491
492       // save value for next iteration delta comparison
493       pxPinchZoom  = px_pinch_zoom;
494       do_zoommove  = true;
495     }
496     else if ( motionDzoomId === motion_id ) {
497       if ( bound_ns_map.uzoommove ) {
498         px_delta_zoom = motion_map.px_delta_y * zoomMouseNum;
499         do_zoommove = true;
500       }
501     }
502
503     if ( do_zoommove ){
504       event_ue = $.Event('uzoommove');
505       motion_map.px_delta_zoom = px_delta_zoom;
506       $.extend( event_ue, motion_map );
507       $(motion_map.elem_bound).trigger(event_ue);
508       return;
509     }
510
511     if ( motionHeldId === motion_id ) {
512       if ( bound_ns_map.uheldmove ) {
513         event_ue = $.Event('uheldmove');
514         $.extend( event_ue, motion_map );
515         $(motion_map.elem_bound).trigger(event_ue);
516         event_src.preventDefault();
517       }
518     }
519     else if ( motionDragId === motion_id ) {
520       if ( bound_ns_map.udragmove ) {
521         event_ue = $.Event('udragmove');
522         $.extend( event_ue, motion_map );
523         $(motion_map.elem_bound).trigger(event_ue);
524         event_src.preventDefault();
525       }
526     }
527
528     if ( ! motionDragId
529       && ! motionHeldId
530       && bound_ns_map.udragstart
531       && motion_map.do_allow_tap === false
532     ) {
533       motionDragId = motion_id;
534       event_ue = $.Event('udragstart');
535       $.extend( event_ue, motion_map );
536       $(motion_map.elem_bound).trigger(event_ue);
537       event_src.preventDefault();
538
539       if ( motion_map.idto_tapheld ) {
540         clearTimeout(motion_map.idto_tapheld);
541         delete motion_map.idto_tapheld;
542       }
543     }
544   };
545   // End motion control /fnMotionMove/
546
547   // Begin motion control /fnMotionEnd/
548   fnMotionEnd   = function ( arg_map ) {
549     var
550       motion_id    = arg_map.motion_id,
551       event_src    = arg_map.event_src,
552       do_zoomend   = false,
553       motion_map, option_map, bound_ns_map, event_ue
554       ;
555
556     doDisableMouse = false;
557
558     if ( ! motionMapMap[motion_id] ) { return; }
559
560     motion_map   = motionMapMap[motion_id];
561     option_map   = motion_map.option_map;
562     bound_ns_map = option_map.bound_ns_map;
563
564     motion_map.elem_target  = event_src.target;
565     motion_map.ms_elapsed   = event_src.timeStamp - motion_map.ms_timestart;
566     motion_map.ms_timestop  = event_src.timeStamp;
567
568     if ( motion_map.px_current_x ) {
569       motion_map.px_delta_x   = event_src.clientX - motion_map.px_current_x;
570       motion_map.px_delta_y   = event_src.clientY - motion_map.px_current_y;
571     }
572
573     motion_map.px_current_x = event_src.clientX;
574     motion_map.px_current_y = event_src.clientY;
575
576     motion_map.px_end_x     = event_src.clientX;
577     motion_map.px_end_y     = event_src.clientY;
578
579     // native event object override
580     motion_map.timeStamp    = event_src.timeStamp
581     ;
582
583     // clear-out any long-hold tap timer
584     if ( motion_map.idto_tapheld ) {
585       clearTimeout(motion_map.idto_tapheld);
586       delete motion_map.idto_tapheld;
587     }
588
589     // trigger utap
590     if ( bound_ns_map.utap
591       && motion_map.ms_elapsed   <= option_map.tap_time
592       && motion_map.do_allow_tap
593     ) {
594       event_ue = $.Event('utap');
595       $.extend( event_ue, motion_map );
596       $(motion_map.elem_bound).trigger(event_ue);
597     }
598
599     // trigger udragend
600     if ( motion_id === motionDragId ) {
601       if ( bound_ns_map.udragend ) {
602         event_ue = $.Event('udragend');
603         $.extend( event_ue, motion_map );
604         $(motion_map.elem_bound).trigger(event_ue);
605         event_src.preventDefault();
606       }
607       motionDragId = undefined;
608     }
609
610     // trigger heldend
611     if ( motion_id === motionHeldId ) {
612       if ( bound_ns_map.uheldend ) {
613         event_ue = $.Event('uheldend');
614         $.extend( event_ue, motion_map );
615         $(motion_map.elem_bound).trigger(event_ue);
616       }
617       motionHeldId = undefined;
618     }
619
620     // trigger uzoomend
621     if ( motion_id === motionDzoomId ) {
622       do_zoomend = true;
623       motionDzoomId = undefined;
624     }
625
626     // cleanup zoom info
627     else if ( motion_id === motion1ZoomId ) {
628       if ( motion2ZoomId ) {
629         motion1ZoomId = motion2ZoomId;
630         motion2ZoomId = undefined;
631         do_zoomend = true;
632       }
633       else { motion1ZoomId = undefined; }
634       pxPinchZoom  = -1;
635     }
636     if ( motion_id === motion2ZoomId ) {
637       motion2ZoomId = undefined;
638       pxPinchZoom  = -1;
639       do_zoomend   = true;
640     }
641
642     if ( do_zoomend && bound_ns_map.uzoomend ) {
643       event_ue = $.Event('uzoomend');
644       motion_map.px_delta_zoom = 0;
645       $.extend( event_ue, motion_map );
646       $(motion_map.elem_bound).trigger(event_ue);
647     }
648     // remove pointer from consideration
649     delete motionMapMap[motion_id];
650   };
651   // End motion control /fnMotionEnd/
652   //------------------ END MOTION CONTROLS -------------------
653
654  //------------------- BEGIN EVENT HANDLERS -------------------
655   // Begin event handler /onTouch/ for all touch events.
656   // We use the 'type' attribute to dispatch to motion control
657   onTouch = function ( event ) {
658     var
659       this_el     = this,
660       timestamp   = +new Date(),
661       o_event     = event.originalEvent,
662       touch_list  = o_event ? o_event.changedTouches || [] : [],
663       touch_count = touch_list.length,
664       idx, touch_event, motion_id, handler_fn
665       ;
666
667     doDisableMouse = true;
668
669     event.timeStamp = timestamp;
670
671     switch ( event.type ) {
672       case 'touchstart' :
673         handler_fn = fnMotionStart;
674         event.preventDefault();
675       break;
676       case 'touchmove'  :
677         handler_fn = fnMotionMove;
678       break;
679       case 'touchend'    : 
680       case 'touchcancel' : handler_fn = fnMotionEnd;   break;
681       default : handler_fn = null;
682     }
683
684     if ( ! handler_fn ) { return; }
685
686     for ( idx = 0; idx < touch_count; idx++ ) {
687       touch_event  = touch_list[idx];
688
689       motion_id = 'touch' + String(touch_event.identifier);
690
691       event.clientX   = touch_event.clientX;
692       event.clientY   = touch_event.clientY;
693       handler_fn({
694         elem      : this_el,
695         motion_id : motion_id,
696         event_src : event
697       });
698     }
699   };
700   // End event handler /onTouch/
701
702
703   // Begin event handler /onMouse/ for all mouse events
704   // We use the 'type' attribute to dispatch to motion control
705   onMouse = function ( event ) {
706     var
707       this_el       = this,
708       motion_id     = 'mouse' + String(event.button),
709       request_dzoom = false,
710       handler_fn
711       ;
712
713     if ( doDisableMouse ) {
714       event.stopImmediatePropagation();
715       return;
716     }
717
718     if ( event.shiftKey ) { request_dzoom  =  true; }
719
720     // skip left or middle clicks
721     if ( event.type !== 'mousemove' ) {
722       if ( event.button !== 0 ) { return true; }
723     }
724
725     switch ( event.type ) {
726       case 'mousedown' :
727         handler_fn = fnMotionStart;
728         event.preventDefault();
729         break;
730       case 'mouseup'   :
731         handler_fn = fnMotionEnd;
732         break;
733       case 'mousemove' :
734         handler_fn = fnMotionMove;
735         break;
736       default:
737         handler_fn = null;
738     }
739
740     if ( ! handler_fn ) { return; }
741
742     handler_fn({
743       elem          : this_el,
744       event_src     : event,
745       request_dzoom : request_dzoom,
746       motion_id     : motion_id
747     });
748   };
749   // End event handler /onMouse/
750   //-------------------- END EVENT HANDLERS --------------------
751
752
753   // Export special events through jQuery API
754   $Special.ue
755     = $Special.utap       = $Special.uheld
756     = $Special.uzoomstart = $Special.uzoommove = $Special.uzoomend
757     = $Special.udragstart = $Special.udragmove = $Special.udragend
758     = $Special.uheldstart = $Special.uheldmove = $Special.uheldend
759     = Ue
760     ;
761   $.ueSetGlobalCb = function ( selector_str, callback_match, callback_nomatch ) {
762     callbackList.push( {
763       selector_str     : selector_str     || '',
764       callback_match   : callback_match   || null,
765       callback_nomatch : callback_nomatch || null
766     });
767   };
768
769 }(jQuery));