PageRenderTime 42ms CodeModel.GetById 17ms app.highlight 20ms RepoModel.GetById 1ms app.codeStats 0ms

/static/scripts/jquery.event.drag.js

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