302 lines
9.2 KiB
JavaScript
302 lines
9.2 KiB
JavaScript
/*!
|
|
* jquery.event.drop - v 2.2
|
|
* Copyright (c) 2010 Three Dub Media - http://threedubmedia.com
|
|
* Open Source MIT License - http://threedubmedia.com/code/license
|
|
*/
|
|
// Created: 2008-06-04
|
|
// Updated: 2012-05-21
|
|
// REQUIRES: jquery 1.7.x, event.drag 2.2
|
|
|
|
;(function($){ // secure $ jQuery alias
|
|
|
|
// Events: drop, dropstart, dropend
|
|
|
|
// add the jquery instance method
|
|
$.fn.drop = function( str, arg, opts ){
|
|
// figure out the event type
|
|
var type = typeof str == "string" ? str : "",
|
|
// figure out the event handler...
|
|
fn = $.isFunction( str ) ? str : $.isFunction( arg ) ? arg : null;
|
|
// fix the event type
|
|
if ( type.indexOf("drop") !== 0 )
|
|
type = "drop"+ type;
|
|
// were options passed
|
|
opts = ( str == fn ? arg : opts ) || {};
|
|
// trigger or bind event handler
|
|
return fn ? this.bind( type, opts, fn ) : this.trigger( type );
|
|
};
|
|
|
|
// DROP MANAGEMENT UTILITY
|
|
// returns filtered drop target elements, caches their positions
|
|
$.drop = function( opts ){
|
|
opts = opts || {};
|
|
// safely set new options...
|
|
drop.multi = opts.multi === true ? Infinity :
|
|
opts.multi === false ? 1 : !isNaN( opts.multi ) ? opts.multi : drop.multi;
|
|
drop.delay = opts.delay || drop.delay;
|
|
drop.tolerance = $.isFunction( opts.tolerance ) ? opts.tolerance :
|
|
opts.tolerance === null ? null : drop.tolerance;
|
|
drop.mode = opts.mode || drop.mode || 'intersect';
|
|
};
|
|
|
|
// local refs (increase compression)
|
|
var $event = $.event,
|
|
$special = $event.special,
|
|
// configure the drop special event
|
|
drop = $.event.special.drop = {
|
|
|
|
// these are the default settings
|
|
multi: 1, // allow multiple drop winners per dragged element
|
|
delay: 20, // async timeout delay
|
|
mode: 'overlap', // drop tolerance mode
|
|
|
|
// internal cache
|
|
targets: [],
|
|
|
|
// the key name for stored drop data
|
|
datakey: "dropdata",
|
|
|
|
// prevent bubbling for better performance
|
|
noBubble: true,
|
|
|
|
// count bound related events
|
|
add: function( obj ){
|
|
// read the interaction data
|
|
var data = $.data( this, drop.datakey );
|
|
// count another realted event
|
|
data.related += 1;
|
|
},
|
|
|
|
// forget unbound related events
|
|
remove: function(){
|
|
$.data( this, drop.datakey ).related -= 1;
|
|
},
|
|
|
|
// configure the interactions
|
|
setup: function(){
|
|
// check for related events
|
|
if ( $.data( this, drop.datakey ) )
|
|
return;
|
|
// initialize the drop element data
|
|
var data = {
|
|
related: 0,
|
|
active: [],
|
|
anyactive: 0,
|
|
winner: 0,
|
|
location: {}
|
|
};
|
|
// store the drop data on the element
|
|
$.data( this, drop.datakey, data );
|
|
// store the drop target in internal cache
|
|
drop.targets.push( this );
|
|
},
|
|
|
|
// destroy the configure interaction
|
|
teardown: function(){
|
|
var data = $.data( this, drop.datakey ) || {};
|
|
// check for related events
|
|
if ( data.related )
|
|
return;
|
|
// remove the stored data
|
|
$.removeData( this, drop.datakey );
|
|
// reference the targeted element
|
|
var element = this;
|
|
// remove from the internal cache
|
|
drop.targets = $.grep( drop.targets, function( target ){
|
|
return ( target !== element );
|
|
});
|
|
},
|
|
|
|
// shared event handler
|
|
handler: function( event, dd ){
|
|
// local vars
|
|
var results, $targets;
|
|
// make sure the right data is available
|
|
if ( !dd )
|
|
return;
|
|
// handle various events
|
|
switch ( event.type ){
|
|
// draginit, from $.event.special.drag
|
|
case 'mousedown': // DROPINIT >>
|
|
case 'touchstart': // DROPINIT >>
|
|
// collect and assign the drop targets
|
|
$targets = $( drop.targets );
|
|
if ( typeof dd.drop == "string" )
|
|
$targets = $targets.filter( dd.drop );
|
|
// reset drop data winner properties
|
|
$targets.each(function(){
|
|
var data = $.data( this, drop.datakey );
|
|
data.active = [];
|
|
data.anyactive = 0;
|
|
data.winner = 0;
|
|
});
|
|
// set available target elements
|
|
dd.droppable = $targets;
|
|
// activate drop targets for the initial element being dragged
|
|
$special.drag.hijack( event, "dropinit", dd );
|
|
break;
|
|
// drag, from $.event.special.drag
|
|
case 'mousemove': // TOLERATE >>
|
|
case 'touchmove': // TOLERATE >>
|
|
drop.event = event; // store the mousemove event
|
|
if ( !drop.timer )
|
|
// monitor drop targets
|
|
drop.tolerate( dd );
|
|
break;
|
|
// dragend, from $.event.special.drag
|
|
case 'mouseup': // DROP >> DROPEND >>
|
|
case 'touchend': // DROP >> DROPEND >>
|
|
drop.timer = clearTimeout( drop.timer ); // delete timer
|
|
if ( dd.propagates ){
|
|
$special.drag.hijack( event, "drop", dd );
|
|
$special.drag.hijack( event, "dropend", dd );
|
|
}
|
|
break;
|
|
|
|
}
|
|
},
|
|
|
|
// returns the location positions of an element
|
|
locate: function( elem, index ){
|
|
var data = $.data( elem, drop.datakey ),
|
|
$elem = $( elem ),
|
|
posi = $elem.offset() || {},
|
|
height = $elem.outerHeight(),
|
|
width = $elem.outerWidth(),
|
|
location = {
|
|
elem: elem,
|
|
width: width,
|
|
height: height,
|
|
top: posi.top,
|
|
left: posi.left,
|
|
right: posi.left + width,
|
|
bottom: posi.top + height
|
|
};
|
|
// drag elements might not have dropdata
|
|
if ( data ){
|
|
data.location = location;
|
|
data.index = index;
|
|
data.elem = elem;
|
|
}
|
|
return location;
|
|
},
|
|
|
|
// test the location positions of an element against another OR an X,Y coord
|
|
contains: function( target, test ){ // target { location } contains test [x,y] or { location }
|
|
return ( ( test[0] || test.left ) >= target.left && ( test[0] || test.right ) <= target.right
|
|
&& ( test[1] || test.top ) >= target.top && ( test[1] || test.bottom ) <= target.bottom );
|
|
},
|
|
|
|
// stored tolerance modes
|
|
modes: { // fn scope: "$.event.special.drop" object
|
|
// target with mouse wins, else target with most overlap wins
|
|
'intersect': function( event, proxy, target ){
|
|
return this.contains( target, [ event.pageX, event.pageY ] ) ? // check cursor
|
|
1e9 : this.modes.overlap.apply( this, arguments ); // check overlap
|
|
},
|
|
// target with most overlap wins
|
|
'overlap': function( event, proxy, target ){
|
|
// calculate the area of overlap...
|
|
return Math.max( 0, Math.min( target.bottom, proxy.bottom ) - Math.max( target.top, proxy.top ) )
|
|
* Math.max( 0, Math.min( target.right, proxy.right ) - Math.max( target.left, proxy.left ) );
|
|
},
|
|
// proxy is completely contained within target bounds
|
|
'fit': function( event, proxy, target ){
|
|
return this.contains( target, proxy ) ? 1 : 0;
|
|
},
|
|
// center of the proxy is contained within target bounds
|
|
'middle': function( event, proxy, target ){
|
|
return this.contains( target, [ proxy.left + proxy.width * .5, proxy.top + proxy.height * .5 ] ) ? 1 : 0;
|
|
}
|
|
},
|
|
|
|
// sort drop target cache by by winner (dsc), then index (asc)
|
|
sort: function( a, b ){
|
|
return ( b.winner - a.winner ) || ( a.index - b.index );
|
|
},
|
|
|
|
// async, recursive tolerance execution
|
|
tolerate: function( dd ){
|
|
// declare local refs
|
|
var i, drp, drg, data, arr, len, elem,
|
|
// interaction iteration variables
|
|
x = 0, ia, end = dd.interactions.length,
|
|
// determine the mouse coords
|
|
xy = [ drop.event.pageX, drop.event.pageY ],
|
|
// custom or stored tolerance fn
|
|
tolerance = drop.tolerance || drop.modes[ drop.mode ];
|
|
// go through each passed interaction...
|
|
do if ( ia = dd.interactions[x] ){
|
|
// check valid interaction
|
|
if ( !ia )
|
|
return;
|
|
// initialize or clear the drop data
|
|
ia.drop = [];
|
|
// holds the drop elements
|
|
arr = [];
|
|
len = ia.droppable.length;
|
|
// determine the proxy location, if needed
|
|
if ( tolerance )
|
|
drg = drop.locate( ia.proxy );
|
|
// reset the loop
|
|
i = 0;
|
|
// loop each stored drop target
|
|
do if ( elem = ia.droppable[i] ){
|
|
data = $.data( elem, drop.datakey );
|
|
drp = data.location;
|
|
if ( !drp ) continue;
|
|
// find a winner: tolerance function is defined, call it
|
|
data.winner = tolerance ? tolerance.call( drop, drop.event, drg, drp )
|
|
// mouse position is always the fallback
|
|
: drop.contains( drp, xy ) ? 1 : 0;
|
|
arr.push( data );
|
|
} while ( ++i < len ); // loop
|
|
// sort the drop targets
|
|
arr.sort( drop.sort );
|
|
// reset the loop
|
|
i = 0;
|
|
// loop through all of the targets again
|
|
do if ( data = arr[ i ] ){
|
|
// winners...
|
|
if ( data.winner && ia.drop.length < drop.multi ){
|
|
// new winner... dropstart
|
|
if ( !data.active[x] && !data.anyactive ){
|
|
// check to make sure that this is not prevented
|
|
if ( $special.drag.hijack( drop.event, "dropstart", dd, x, data.elem )[0] !== false ){
|
|
data.active[x] = 1;
|
|
data.anyactive += 1;
|
|
}
|
|
// if false, it is not a winner
|
|
else
|
|
data.winner = 0;
|
|
}
|
|
// if it is still a winner
|
|
if ( data.winner )
|
|
ia.drop.push( data.elem );
|
|
}
|
|
// losers...
|
|
else if ( data.active[x] && data.anyactive == 1 ){
|
|
// former winner... dropend
|
|
$special.drag.hijack( drop.event, "dropend", dd, x, data.elem );
|
|
data.active[x] = 0;
|
|
data.anyactive -= 1;
|
|
}
|
|
} while ( ++i < len ); // loop
|
|
} while ( ++x < end ) // loop
|
|
// check if the mouse is still moving or is idle
|
|
if ( drop.last && xy[0] == drop.last.pageX && xy[1] == drop.last.pageY )
|
|
delete drop.timer; // idle, don't recurse
|
|
else // recurse
|
|
drop.timer = setTimeout(function(){
|
|
drop.tolerate( dd );
|
|
}, drop.delay );
|
|
// remember event, to compare idleness
|
|
drop.last = drop.event;
|
|
}
|
|
|
|
};
|
|
|
|
// share the same special event configuration with related events...
|
|
$special.dropinit = $special.dropstart = $special.dropend = drop;
|
|
|
|
})(jQuery); // confine scope
|