first step for manual merging of the slickgrid branch
[myslice.git] / third-party / slickgrid-2.1 / lib / jquery.event.drag-2.2.js
1 /*! 
2  * jquery.event.drag - v 2.2
3  * Copyright (c) 2010 Three Dub Media - http://threedubmedia.com
4  * Open Source MIT License - http://threedubmedia.com/code/license
5  */
6 // Created: 2008-06-04 
7 // Updated: 2012-05-21
8 // REQUIRES: jquery 1.7.x
9
10 ;(function( $ ){
11
12 // add the jquery instance method
13 $.fn.drag = function( str, arg, opts ){
14         // figure out the event type
15         var type = typeof str == "string" ? str : "",
16         // figure out the event handler...
17         fn = $.isFunction( str ) ? str : $.isFunction( arg ) ? arg : null;
18         // fix the event type
19         if ( type.indexOf("drag") !== 0 ) 
20                 type = "drag"+ type;
21         // were options passed
22         opts = ( str == fn ? arg : opts ) || {};
23         // trigger or bind event handler
24         return fn ? this.bind( type, opts, fn ) : this.trigger( type );
25 };
26
27 // local refs (increase compression)
28 var $event = $.event, 
29 $special = $event.special,
30 // configure the drag special event 
31 drag = $special.drag = {
32         
33         // these are the default settings
34         defaults: {
35                 which: 1, // mouse button pressed to start drag sequence
36                 distance: 0, // distance dragged before dragstart
37                 not: ':input', // selector to suppress dragging on target elements
38                 handle: null, // selector to match handle target elements
39                 relative: false, // true to use "position", false to use "offset"
40                 drop: true, // false to suppress drop events, true or selector to allow
41                 click: false // false to suppress click events after dragend (no proxy)
42         },
43         
44         // the key name for stored drag data
45         datakey: "dragdata",
46         
47         // prevent bubbling for better performance
48         noBubble: true,
49         
50         // count bound related events
51         add: function( obj ){ 
52                 // read the interaction data
53                 var data = $.data( this, drag.datakey ),
54                 // read any passed options 
55                 opts = obj.data || {};
56                 // count another realted event
57                 data.related += 1;
58                 // extend data options bound with this event
59                 // don't iterate "opts" in case it is a node 
60                 $.each( drag.defaults, function( key, def ){
61                         if ( opts[ key ] !== undefined )
62                                 data[ key ] = opts[ key ];
63                 });
64         },
65         
66         // forget unbound related events
67         remove: function(){
68                 $.data( this, drag.datakey ).related -= 1;
69         },
70         
71         // configure interaction, capture settings
72         setup: function(){
73                 // check for related events
74                 if ( $.data( this, drag.datakey ) ) 
75                         return;
76                 // initialize the drag data with copied defaults
77                 var data = $.extend({ related:0 }, drag.defaults );
78                 // store the interaction data
79                 $.data( this, drag.datakey, data );
80                 // bind the mousedown event, which starts drag interactions
81                 $event.add( this, "touchstart mousedown", drag.init, data );
82                 // prevent image dragging in IE...
83                 if ( this.attachEvent ) 
84                         this.attachEvent("ondragstart", drag.dontstart ); 
85         },
86         
87         // destroy configured interaction
88         teardown: function(){
89                 var data = $.data( this, drag.datakey ) || {};
90                 // check for related events
91                 if ( data.related ) 
92                         return;
93                 // remove the stored data
94                 $.removeData( this, drag.datakey );
95                 // remove the mousedown event
96                 $event.remove( this, "touchstart mousedown", drag.init );
97                 // enable text selection
98                 drag.textselect( true ); 
99                 // un-prevent image dragging in IE...
100                 if ( this.detachEvent ) 
101                         this.detachEvent("ondragstart", drag.dontstart ); 
102         },
103                 
104         // initialize the interaction
105         init: function( event ){ 
106                 // sorry, only one touch at a time
107                 if ( drag.touched ) 
108                         return;
109                 // the drag/drop interaction data
110                 var dd = event.data, results;
111                 // check the which directive
112                 if ( event.which != 0 && dd.which > 0 && event.which != dd.which ) 
113                         return; 
114                 // check for suppressed selector
115                 if ( $( event.target ).is( dd.not ) ) 
116                         return;
117                 // check for handle selector
118                 if ( dd.handle && !$( event.target ).closest( dd.handle, event.currentTarget ).length ) 
119                         return;
120
121                 drag.touched = event.type == 'touchstart' ? this : null;
122                 dd.propagates = 1;
123                 dd.mousedown = this;
124                 dd.interactions = [ drag.interaction( this, dd ) ];
125                 dd.target = event.target;
126                 dd.pageX = event.pageX;
127                 dd.pageY = event.pageY;
128                 dd.dragging = null;
129                 // handle draginit event... 
130                 results = drag.hijack( event, "draginit", dd );
131                 // early cancel
132                 if ( !dd.propagates )
133                         return;
134                 // flatten the result set
135                 results = drag.flatten( results );
136                 // insert new interaction elements
137                 if ( results && results.length ){
138                         dd.interactions = [];
139                         $.each( results, function(){
140                                 dd.interactions.push( drag.interaction( this, dd ) );
141                         });
142                 }
143                 // remember how many interactions are propagating
144                 dd.propagates = dd.interactions.length;
145                 // locate and init the drop targets
146                 if ( dd.drop !== false && $special.drop ) 
147                         $special.drop.handler( event, dd );
148                 // disable text selection
149                 drag.textselect( false ); 
150                 // bind additional events...
151                 if ( drag.touched )
152                         $event.add( drag.touched, "touchmove touchend", drag.handler, dd );
153                 else 
154                         $event.add( document, "mousemove mouseup", drag.handler, dd );
155                 // helps prevent text selection or scrolling
156                 if ( !drag.touched || dd.live )
157                         return false;
158         },      
159         
160         // returns an interaction object
161         interaction: function( elem, dd ){
162                 var offset = $( elem )[ dd.relative ? "position" : "offset" ]() || { top:0, left:0 };
163                 return {
164                         drag: elem, 
165                         callback: new drag.callback(), 
166                         droppable: [],
167                         offset: offset
168                 };
169         },
170         
171         // handle drag-releatd DOM events
172         handler: function( event ){ 
173                 // read the data before hijacking anything
174                 var dd = event.data;    
175                 // handle various events
176                 switch ( event.type ){
177                         // mousemove, check distance, start dragging
178                         case !dd.dragging && 'touchmove': 
179                                 event.preventDefault();
180                         case !dd.dragging && 'mousemove':
181                                 //  drag tolerance, x≤ + y≤ = distance≤
182                                 if ( Math.pow(  event.pageX-dd.pageX, 2 ) + Math.pow(  event.pageY-dd.pageY, 2 ) < Math.pow( dd.distance, 2 ) ) 
183                                         break; // distance tolerance not reached
184                                 event.target = dd.target; // force target from "mousedown" event (fix distance issue)
185                                 drag.hijack( event, "dragstart", dd ); // trigger "dragstart"
186                                 if ( dd.propagates ) // "dragstart" not rejected
187                                         dd.dragging = true; // activate interaction
188                         // mousemove, dragging
189                         case 'touchmove':
190                                 event.preventDefault();
191                         case 'mousemove':
192                                 if ( dd.dragging ){
193                                         // trigger "drag"               
194                                         drag.hijack( event, "drag", dd );
195                                         if ( dd.propagates ){
196                                                 // manage drop events
197                                                 if ( dd.drop !== false && $special.drop )
198                                                         $special.drop.handler( event, dd ); // "dropstart", "dropend"                                                   
199                                                 break; // "drag" not rejected, stop             
200                                         }
201                                         event.type = "mouseup"; // helps "drop" handler behave
202                                 }
203                         // mouseup, stop dragging
204                         case 'touchend': 
205                         case 'mouseup': 
206                         default:
207                                 if ( drag.touched )
208                                         $event.remove( drag.touched, "touchmove touchend", drag.handler ); // remove touch events
209                                 else 
210                                         $event.remove( document, "mousemove mouseup", drag.handler ); // remove page events     
211                                 if ( dd.dragging ){
212                                         if ( dd.drop !== false && $special.drop )
213                                                 $special.drop.handler( event, dd ); // "drop"
214                                         drag.hijack( event, "dragend", dd ); // trigger "dragend"       
215                                 }
216                                 drag.textselect( true ); // enable text selection
217                                 // if suppressing click events...
218                                 if ( dd.click === false && dd.dragging )
219                                         $.data( dd.mousedown, "suppress.click", new Date().getTime() + 5 );
220                                 dd.dragging = drag.touched = false; // deactivate element       
221                                 break;
222                 }
223         },
224                 
225         // re-use event object for custom events
226         hijack: function( event, type, dd, x, elem ){
227                 // not configured
228                 if ( !dd ) 
229                         return;
230                 // remember the original event and type
231                 var orig = { event:event.originalEvent, type:event.type },
232                 // is the event drag related or drog related?
233                 mode = type.indexOf("drop") ? "drag" : "drop",
234                 // iteration vars
235                 result, i = x || 0, ia, $elems, callback,
236                 len = !isNaN( x ) ? x : dd.interactions.length;
237                 // modify the event type
238                 event.type = type;
239                 // remove the original event
240                 event.originalEvent = null;
241                 // initialize the results
242                 dd.results = [];
243                 // handle each interacted element
244                 do if ( ia = dd.interactions[ i ] ){
245                         // validate the interaction
246                         if ( type !== "dragend" && ia.cancelled )
247                                 continue;
248                         // set the dragdrop properties on the event object
249                         callback = drag.properties( event, dd, ia );
250                         // prepare for more results
251                         ia.results = [];
252                         // handle each element
253                         $( elem || ia[ mode ] || dd.droppable ).each(function( p, subject ){
254                                 // identify drag or drop targets individually
255                                 callback.target = subject;
256                                 // force propagtion of the custom event
257                                 event.isPropagationStopped = function(){ return false; };
258                                 // handle the event     
259                                 result = subject ? $event.dispatch.call( subject, event, callback ) : null;
260                                 // stop the drag interaction for this element
261                                 if ( result === false ){
262                                         if ( mode == "drag" ){
263                                                 ia.cancelled = true;
264                                                 dd.propagates -= 1;
265                                         }
266                                         if ( type == "drop" ){
267                                                 ia[ mode ][p] = null;
268                                         }
269                                 }
270                                 // assign any dropinit elements
271                                 else if ( type == "dropinit" )
272                                         ia.droppable.push( drag.element( result ) || subject );
273                                 // accept a returned proxy element 
274                                 if ( type == "dragstart" )
275                                         ia.proxy = $( drag.element( result ) || ia.drag )[0];
276                                 // remember this result 
277                                 ia.results.push( result );
278                                 // forget the event result, for recycling
279                                 delete event.result;
280                                 // break on cancelled handler
281                                 if ( type !== "dropinit" )
282                                         return result;
283                         });     
284                         // flatten the results  
285                         dd.results[ i ] = drag.flatten( ia.results );   
286                         // accept a set of valid drop targets
287                         if ( type == "dropinit" )
288                                 ia.droppable = drag.flatten( ia.droppable );
289                         // locate drop targets
290                         if ( type == "dragstart" && !ia.cancelled )
291                                 callback.update(); 
292                 }
293                 while ( ++i < len )
294                 // restore the original event & type
295                 event.type = orig.type;
296                 event.originalEvent = orig.event;
297                 // return all handler results
298                 return drag.flatten( dd.results );
299         },
300                 
301         // extend the callback object with drag/drop properties...
302         properties: function( event, dd, ia ){          
303                 var obj = ia.callback;
304                 // elements
305                 obj.drag = ia.drag;
306                 obj.proxy = ia.proxy || ia.drag;
307                 // starting mouse position
308                 obj.startX = dd.pageX;
309                 obj.startY = dd.pageY;
310                 // current distance dragged
311                 obj.deltaX = event.pageX - dd.pageX;
312                 obj.deltaY = event.pageY - dd.pageY;
313                 // original element position
314                 obj.originalX = ia.offset.left;
315                 obj.originalY = ia.offset.top;
316                 // adjusted element position
317                 obj.offsetX = obj.originalX + obj.deltaX; 
318                 obj.offsetY = obj.originalY + obj.deltaY;
319                 // assign the drop targets information
320                 obj.drop = drag.flatten( ( ia.drop || [] ).slice() );
321                 obj.available = drag.flatten( ( ia.droppable || [] ).slice() );
322                 return obj;     
323         },
324         
325         // determine is the argument is an element or jquery instance
326         element: function( arg ){
327                 if ( arg && ( arg.jquery || arg.nodeType == 1 ) )
328                         return arg;
329         },
330         
331         // flatten nested jquery objects and arrays into a single dimension array
332         flatten: function( arr ){
333                 return $.map( arr, function( member ){
334                         return member && member.jquery ? $.makeArray( member ) : 
335                                 member && member.length ? drag.flatten( member ) : member;
336                 });
337         },
338         
339         // toggles text selection attributes ON (true) or OFF (false)
340         textselect: function( bool ){ 
341                 $( document )[ bool ? "unbind" : "bind" ]("selectstart", drag.dontstart )
342                         .css("MozUserSelect", bool ? "" : "none" );
343                 // .attr("unselectable", bool ? "off" : "on" )
344                 document.unselectable = bool ? "off" : "on"; 
345         },
346         
347         // suppress "selectstart" and "ondragstart" events
348         dontstart: function(){ 
349                 return false; 
350         },
351         
352         // a callback instance contructor
353         callback: function(){}
354         
355 };
356
357 // callback methods
358 drag.callback.prototype = {
359         update: function(){
360                 if ( $special.drop && this.available.length )
361                         $.each( this.available, function( i ){
362                                 $special.drop.locate( this, i );
363                         });
364         }
365 };
366
367 // patch $.event.$dispatch to allow suppressing clicks
368 var $dispatch = $event.dispatch;
369 $event.dispatch = function( event ){
370         if ( $.data( this, "suppress."+ event.type ) - new Date().getTime() > 0 ){
371                 $.removeData( this, "suppress."+ event.type );
372                 return;
373         }
374         return $dispatch.apply( this, arguments );
375 };
376
377 // event fix hooks for touch events...
378 var touchHooks = 
379 $event.fixHooks.touchstart = 
380 $event.fixHooks.touchmove = 
381 $event.fixHooks.touchend =
382 $event.fixHooks.touchcancel = {
383         props: "clientX clientY pageX pageY screenX screenY".split( " " ),
384         filter: function( event, orig ) {
385                 if ( orig ){
386                         var touched = ( orig.touches && orig.touches[0] )
387                                 || ( orig.changedTouches && orig.changedTouches[0] )
388                                 || null; 
389                         // iOS webkit: touchstart, touchmove, touchend
390                         if ( touched ) 
391                                 $.each( touchHooks.props, function( i, prop ){
392                                         event[ prop ] = touched[ prop ];
393                                 });
394                 }
395                 return event;
396         }
397 };
398
399 // share the same special event configuration with related events...
400 $special.draginit = $special.dragstart = $special.dragend = drag;
401
402 })( jQuery );