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 ); |
---|