2 * jquery.event.drop - v 2.2
3 * Copyright (c) 2010 Three Dub Media - http://threedubmedia.com
4 * Open Source MIT License - http://threedubmedia.com/code/license
8 // REQUIRES: jquery 1.7.x, event.drag 2.2
10 ;(function($){ // secure $ jQuery alias
12 // Events: drop, dropstart, dropend
14 // add the jquery instance method
15 $.fn.drop = function( str, arg, opts ){
16 // figure out the event type
17 var type = typeof str == "string" ? str : "",
18 // figure out the event handler...
19 fn = $.isFunction( str ) ? str : $.isFunction( arg ) ? arg : null;
21 if ( type.indexOf("drop") !== 0 )
23 // were options passed
24 opts = ( str == fn ? arg : opts ) || {};
25 // trigger or bind event handler
26 return fn ? this.bind( type, opts, fn ) : this.trigger( type );
29 // DROP MANAGEMENT UTILITY
30 // returns filtered drop target elements, caches their positions
31 $.drop = function( opts ){
33 // safely set new options...
34 drop.multi = opts.multi === true ? Infinity :
35 opts.multi === false ? 1 : !isNaN( opts.multi ) ? opts.multi : drop.multi;
36 drop.delay = opts.delay || drop.delay;
37 drop.tolerance = $.isFunction( opts.tolerance ) ? opts.tolerance :
38 opts.tolerance === null ? null : drop.tolerance;
39 drop.mode = opts.mode || drop.mode || 'intersect';
42 // local refs (increase compression)
44 $special = $event.special,
45 // configure the drop special event
46 drop = $.event.special.drop = {
48 // these are the default settings
49 multi: 1, // allow multiple drop winners per dragged element
50 delay: 20, // async timeout delay
51 mode: 'overlap', // drop tolerance mode
56 // the key name for stored drop data
59 // prevent bubbling for better performance
62 // count bound related events
64 // read the interaction data
65 var data = $.data( this, drop.datakey );
66 // count another realted event
70 // forget unbound related events
72 $.data( this, drop.datakey ).related -= 1;
75 // configure the interactions
77 // check for related events
78 if ( $.data( this, drop.datakey ) )
80 // initialize the drop element data
88 // store the drop data on the element
89 $.data( this, drop.datakey, data );
90 // store the drop target in internal cache
91 drop.targets.push( this );
94 // destroy the configure interaction
96 var data = $.data( this, drop.datakey ) || {};
97 // check for related events
100 // remove the stored data
101 $.removeData( this, drop.datakey );
102 // reference the targeted element
104 // remove from the internal cache
105 drop.targets = $.grep( drop.targets, function( target ){
106 return ( target !== element );
110 // shared event handler
111 handler: function( event, dd ){
113 var results, $targets;
114 // make sure the right data is available
117 // handle various events
118 switch ( event.type ){
119 // draginit, from $.event.special.drag
120 case 'mousedown': // DROPINIT >>
121 case 'touchstart': // DROPINIT >>
122 // collect and assign the drop targets
123 $targets = $( drop.targets );
124 if ( typeof dd.drop == "string" )
125 $targets = $targets.filter( dd.drop );
126 // reset drop data winner properties
127 $targets.each(function(){
128 var data = $.data( this, drop.datakey );
133 // set available target elements
134 dd.droppable = $targets;
135 // activate drop targets for the initial element being dragged
136 $special.drag.hijack( event, "dropinit", dd );
138 // drag, from $.event.special.drag
139 case 'mousemove': // TOLERATE >>
140 case 'touchmove': // TOLERATE >>
141 drop.event = event; // store the mousemove event
143 // monitor drop targets
146 // dragend, from $.event.special.drag
147 case 'mouseup': // DROP >> DROPEND >>
148 case 'touchend': // DROP >> DROPEND >>
149 drop.timer = clearTimeout( drop.timer ); // delete timer
150 if ( dd.propagates ){
151 $special.drag.hijack( event, "drop", dd );
152 $special.drag.hijack( event, "dropend", dd );
159 // returns the location positions of an element
160 locate: function( elem, index ){
161 var data = $.data( elem, drop.datakey ),
163 posi = $elem.offset() || {},
164 height = $elem.outerHeight(),
165 width = $elem.outerWidth(),
172 right: posi.left + width,
173 bottom: posi.top + height
175 // drag elements might not have dropdata
177 data.location = location;
184 // test the location positions of an element against another OR an X,Y coord
185 contains: function( target, test ){ // target { location } contains test [x,y] or { location }
186 return ( ( test[0] || test.left ) >= target.left && ( test[0] || test.right ) <= target.right
187 && ( test[1] || test.top ) >= target.top && ( test[1] || test.bottom ) <= target.bottom );
190 // stored tolerance modes
191 modes: { // fn scope: "$.event.special.drop" object
192 // target with mouse wins, else target with most overlap wins
193 'intersect': function( event, proxy, target ){
194 return this.contains( target, [ event.pageX, event.pageY ] ) ? // check cursor
195 1e9 : this.modes.overlap.apply( this, arguments ); // check overlap
197 // target with most overlap wins
198 'overlap': function( event, proxy, target ){
199 // calculate the area of overlap...
200 return Math.max( 0, Math.min( target.bottom, proxy.bottom ) - Math.max( target.top, proxy.top ) )
201 * Math.max( 0, Math.min( target.right, proxy.right ) - Math.max( target.left, proxy.left ) );
203 // proxy is completely contained within target bounds
204 'fit': function( event, proxy, target ){
205 return this.contains( target, proxy ) ? 1 : 0;
207 // center of the proxy is contained within target bounds
208 'middle': function( event, proxy, target ){
209 return this.contains( target, [ proxy.left + proxy.width * .5, proxy.top + proxy.height * .5 ] ) ? 1 : 0;
213 // sort drop target cache by by winner (dsc), then index (asc)
214 sort: function( a, b ){
215 return ( b.winner - a.winner ) || ( a.index - b.index );
218 // async, recursive tolerance execution
219 tolerate: function( dd ){
220 // declare local refs
221 var i, drp, drg, data, arr, len, elem,
222 // interaction iteration variables
223 x = 0, ia, end = dd.interactions.length,
224 // determine the mouse coords
225 xy = [ drop.event.pageX, drop.event.pageY ],
226 // custom or stored tolerance fn
227 tolerance = drop.tolerance || drop.modes[ drop.mode ];
228 // go through each passed interaction...
229 do if ( ia = dd.interactions[x] ){
230 // check valid interaction
233 // initialize or clear the drop data
235 // holds the drop elements
237 len = ia.droppable.length;
238 // determine the proxy location, if needed
240 drg = drop.locate( ia.proxy );
243 // loop each stored drop target
244 do if ( elem = ia.droppable[i] ){
245 data = $.data( elem, drop.datakey );
247 if ( !drp ) continue;
248 // find a winner: tolerance function is defined, call it
249 data.winner = tolerance ? tolerance.call( drop, drop.event, drg, drp )
250 // mouse position is always the fallback
251 : drop.contains( drp, xy ) ? 1 : 0;
253 } while ( ++i < len ); // loop
254 // sort the drop targets
255 arr.sort( drop.sort );
258 // loop through all of the targets again
259 do if ( data = arr[ i ] ){
261 if ( data.winner && ia.drop.length < drop.multi ){
262 // new winner... dropstart
263 if ( !data.active[x] && !data.anyactive ){
264 // check to make sure that this is not prevented
265 if ( $special.drag.hijack( drop.event, "dropstart", dd, x, data.elem )[0] !== false ){
269 // if false, it is not a winner
273 // if it is still a winner
275 ia.drop.push( data.elem );
278 else if ( data.active[x] && data.anyactive == 1 ){
279 // former winner... dropend
280 $special.drag.hijack( drop.event, "dropend", dd, x, data.elem );
284 } while ( ++i < len ); // loop
285 } while ( ++x < end ) // loop
286 // check if the mouse is still moving or is idle
287 if ( drop.last && xy[0] == drop.last.pageX && xy[1] == drop.last.pageY )
288 delete drop.timer; // idle, don't recurse
290 drop.timer = setTimeout(function(){
293 // remember event, to compare idleness
294 drop.last = drop.event;
299 // share the same special event configuration with related events...
300 $special.dropinit = $special.dropstart = $special.dropend = drop;
302 })(jQuery); // confine scope