[2] | 1 | /*! jquery.event.drop.js * v1.2
|
---|
| 2 | Copyright (c) 2008-2009, Three Dub Media (http://threedubmedia.com)
|
---|
| 3 | Liscensed under the MIT License (http://threedubmedia.googlecode.com/files/MIT-LICENSE.txt)
|
---|
| 4 | */;(function($){ // secure $ jQuery alias
|
---|
| 5 | // Created: 2008-06-04 | Updated: 2009-03-16
|
---|
| 6 | /*******************************************************************************************/
|
---|
| 7 | // Events: drop, dropstart, dropend
|
---|
| 8 | /*******************************************************************************************/
|
---|
| 9 |
|
---|
| 10 | // JQUERY METHOD
|
---|
| 11 | $.fn.drop = function( fn1, fn2, fn3 ){
|
---|
| 12 | if ( fn2 ) this.bind('dropstart', fn1 ); // 2+ args
|
---|
| 13 | if ( fn3 ) this.bind('dropend', fn3 ); // 3 args
|
---|
| 14 | return !fn1 ? this.trigger('drop') // 0 args
|
---|
| 15 | : this.bind('drop', fn2 ? fn2 : fn1 ); // 1+ args
|
---|
| 16 | };
|
---|
| 17 |
|
---|
| 18 | // DROP MANAGEMENT UTILITY
|
---|
| 19 | $.dropManage = function( opts ){ // return filtered drop target elements, cache their positions
|
---|
| 20 | opts = opts || {};
|
---|
| 21 | // safely set new options...
|
---|
| 22 | drop.data = [];
|
---|
| 23 | drop.filter = opts.filter || '*';
|
---|
| 24 | drop.delay = opts.delay || drop.delay;
|
---|
| 25 | drop.tolerance = opts.tolerance || null;
|
---|
| 26 | drop.mode = opts.mode || drop.mode || 'intersect';
|
---|
| 27 | // return the filtered set of drop targets
|
---|
| 28 | return drop.$targets.filter( drop.filter ).each(function(){
|
---|
| 29 | // locate and store the filtered drop targets
|
---|
| 30 | drop.data[ drop.data.length ] = drop.locate( this );
|
---|
| 31 | });
|
---|
| 32 | };
|
---|
| 33 |
|
---|
| 34 | // local refs
|
---|
| 35 | var $event = $.event, $special = $event.special,
|
---|
| 36 |
|
---|
| 37 | // SPECIAL EVENT CONFIGURATION
|
---|
| 38 | drop = $special.drop = {
|
---|
| 39 | delay: 100, // default frequency to track drop targets
|
---|
| 40 | mode: 'intersect', // default mode to determine valid drop targets
|
---|
| 41 | $targets: $([]), data: [], // storage of drop targets and locations
|
---|
| 42 | setup: function(){
|
---|
| 43 | drop.$targets = drop.$targets.add( this );
|
---|
| 44 | drop.data[ drop.data.length ] = drop.locate( this );
|
---|
| 45 | },
|
---|
| 46 | teardown: function(){ var elem = this;
|
---|
| 47 | drop.$targets = drop.$targets.not( this );
|
---|
| 48 | drop.data = $.grep( drop.data, function( obj ){
|
---|
| 49 | return ( obj.elem !== elem );
|
---|
| 50 | });
|
---|
| 51 | },
|
---|
| 52 | // shared handler
|
---|
| 53 | handler: function( event ){
|
---|
| 54 | var dropstart = null, dropped;
|
---|
| 55 | event.dropTarget = drop.dropping || undefined; // dropped element
|
---|
| 56 | if ( drop.data.length && event.dragTarget ){
|
---|
| 57 | // handle various events
|
---|
| 58 | switch ( event.type ){
|
---|
| 59 | // drag/mousemove, from $.event.special.drag
|
---|
| 60 | case 'drag': // TOLERATE >>
|
---|
| 61 | drop.event = event; // store the mousemove event
|
---|
| 62 | if ( !drop.timer ) // monitor drop targets
|
---|
| 63 | drop.timer = setTimeout( tolerate, 20 );
|
---|
| 64 | break;
|
---|
| 65 | // dragstop/mouseup, from $.event.special.drag
|
---|
| 66 | case 'mouseup': // DROP >> DROPEND >>
|
---|
| 67 | drop.timer = clearTimeout( drop.timer ); // delete timer
|
---|
| 68 | if ( !drop.dropping ) break; // stop, no drop
|
---|
| 69 | if ( drop.allowed )
|
---|
| 70 | dropped = hijack( event, "drop", drop.dropping ); // trigger "drop"
|
---|
| 71 | dropstart = false;
|
---|
| 72 | // activate new target, from tolerate (async)
|
---|
| 73 | case drop.dropping && 'dropstart': // DROPSTART >> ( new target )
|
---|
| 74 | dropstart = dropstart===null && drop.allowed ? true : false;
|
---|
| 75 | // deactivate active target, from tolerate (async)
|
---|
| 76 | case drop.dropping && 'dropend': // DROPEND >>
|
---|
| 77 | hijack( event, "dropend", drop.dropping ); // trigger "dropend"
|
---|
| 78 | drop.dropping = null; // empty dropper
|
---|
| 79 | if ( dropped === false ) event.dropTarget = undefined;
|
---|
| 80 | if ( !dropstart ) break; // stop
|
---|
| 81 | // activate target, from tolerate (async)
|
---|
| 82 | case drop.allowed && 'dropstart': // DROPSTART >>
|
---|
| 83 | event.dropTarget = this;
|
---|
| 84 | drop.dropping = hijack( event, "dropstart", this )!==false ? this : null; // trigger "dropstart"
|
---|
| 85 | break;
|
---|
| 86 | }
|
---|
| 87 | }
|
---|
| 88 | },
|
---|
| 89 | // returns the location positions of an element
|
---|
| 90 | locate: function( elem ){ // return { L:left, R:right, T:top, B:bottom, H:height, W:width }
|
---|
| 91 | var $el = $(elem), pos = $el.offset(), h = $el.outerHeight(), w = $el.outerWidth();
|
---|
| 92 | return { elem: elem, L: pos.left, R: pos.left+w, T: pos.top, B: pos.top+h, W: w, H: h };
|
---|
| 93 | },
|
---|
| 94 | // test the location positions of an element against another OR an X,Y coord
|
---|
| 95 | contains: function( target, test ){ // target { L,R,T,B,H,W } contains test [x,y] or { L,R,T,B,H,W }
|
---|
| 96 | return ( ( test[0] || test.L ) >= target.L && ( test[0] || test.R ) <= target.R
|
---|
| 97 | && ( test[1] || test.T ) >= target.T && ( test[1] || test.B ) <= target.B );
|
---|
| 98 | },
|
---|
| 99 | // stored tolerance modes
|
---|
| 100 | modes: { // fn scope: "$.event.special.drop" object
|
---|
| 101 | // target with mouse wins, else target with most overlap wins
|
---|
| 102 | 'intersect': function( event, proxy, target ){
|
---|
| 103 | return this.contains( target, [ event.pageX, event.pageY ] ) ? // check cursor
|
---|
| 104 | target : this.modes['overlap'].apply( this, arguments ); // check overlap
|
---|
| 105 | },
|
---|
| 106 | // target with most overlap wins
|
---|
| 107 | 'overlap': function( event, proxy, target ){
|
---|
| 108 | // calculate the area of overlap...
|
---|
| 109 | target.overlap = Math.max( 0, Math.min( target.B, proxy.B ) - Math.max( target.T, proxy.T ) )
|
---|
| 110 | * Math.max( 0, Math.min( target.R, proxy.R ) - Math.max( target.L, proxy.L ) );
|
---|
| 111 | if ( target.overlap > ( ( this.best || {} ).overlap || 0 ) ) // compare overlap
|
---|
| 112 | this.best = target; // set as the best match so far
|
---|
| 113 | return null; // no winner
|
---|
| 114 | },
|
---|
| 115 | // proxy is completely contained within target bounds
|
---|
| 116 | 'fit': function( event, proxy, target ){
|
---|
| 117 | return this.contains( target, proxy ) ? target : null;
|
---|
| 118 | },
|
---|
| 119 | // center of the proxy is contained within target bounds
|
---|
| 120 | 'middle': function( event, proxy, target ){
|
---|
| 121 | return this.contains( target, [ proxy.L+proxy.W/2, proxy.T+proxy.H/2 ] ) ? target : null;
|
---|
| 122 | }
|
---|
| 123 | }
|
---|
| 124 | };
|
---|
| 125 |
|
---|
| 126 | // set event type to custom value, and handle it
|
---|
| 127 | function hijack ( event, type, elem ){
|
---|
| 128 | event.type = type; // force the event type
|
---|
| 129 | try { var result = $event.handle.call( elem, event );
|
---|
| 130 | } catch ( ex ){ /* catch IE error with async event handling */ }
|
---|
| 131 | return result===false ? false : result || event.result;
|
---|
| 132 | };
|
---|
| 133 |
|
---|
| 134 | // async, recursive tolerance execution
|
---|
| 135 | function tolerate (){
|
---|
| 136 | var i = 0, drp, winner, // local variables
|
---|
| 137 | xy = [ drop.event.pageX, drop.event.pageY ], // mouse location
|
---|
| 138 | drg = drop.locate( drop.event.dragProxy ); // drag proxy location
|
---|
| 139 | drop.tolerance = drop.tolerance || drop.modes[ drop.mode ]; // custom or stored tolerance fn
|
---|
| 140 | do if ( drp = drop.data[i] ){ // each drop target location
|
---|
| 141 | // tolerance function is defined, or mouse contained
|
---|
| 142 | winner = drop.tolerance ? drop.tolerance.call( drop, drop.event, drg, drp )
|
---|
| 143 | : drop.contains( drp, xy ) ? drp : null; // mouse is always fallback
|
---|
| 144 | }
|
---|
| 145 | while ( ++i<drop.data.length && !winner ); // loop
|
---|
| 146 | drop.event.type = ( winner = winner || drop.best ) ? 'dropstart' : 'dropend'; // start ? stop
|
---|
| 147 | if ( drop.event.type=='dropend' || winner.elem!=drop.dropping ) // don't dropstart on active drop target
|
---|
| 148 | drop.handler.call( winner ? winner.elem : drop.dropping, drop.event ); // handle events
|
---|
| 149 | if ( drop.last && xy[0] == drop.last.pageX && xy[1] == drop.last.pageY ) // no movement
|
---|
| 150 | delete drop.timer; // idle, don't recurse
|
---|
| 151 | else drop.timer = setTimeout( tolerate, drop.delay ); // recurse
|
---|
| 152 | drop.last = drop.event; // to compare idleness
|
---|
| 153 | drop.best = null; // reset comparitors
|
---|
| 154 | };
|
---|
| 155 |
|
---|
| 156 | /*******************************************************************************************/
|
---|
| 157 | })(jQuery); // confine scope |
---|