All Downloads are FREE. Search and download functionalities are using the official Maven repository.

structr.js.lib.test.jquery.simulate.drag-n-drop.js Maven / Gradle / Ivy

Go to download

Structr is an open source framework based on the popular Neo4j graph database.

The newest version!
/*jshint camelcase:true, plusplus:true, forin:true, noarg:true, noempty:true, eqeqeq:true, bitwise:true, strict:true, undef:true, unused:true, curly:true, browser:true, devel:true, maxerr:100, white:false, onevar:false */
/*jslint white: true vars: true browser: true todo: true */
/*global jQuery:true $:true */

/* jQuery Simulate Drag-n-Drop Plugin 1.2.0
 * http://github.com/j-ulrich/jquery-simulate-ext
 * 
 * Copyright (c) 2013 Jochen Ulrich
 * Licensed under the MIT license (MIT-LICENSE.txt).
 */

;(function($, undefined) {
	"use strict";
	
	/* Overwrite the $.fn.simulate function to reduce the jQuery set to the first element for the
	 * drag-n-drop interactions.
	 */
	$.fn.simulate = function( type, options ) {
		switch (type) {
		case "drag":
		case "drop":
		case "drag-n-drop":
			var ele = this.first();
			new $.simulate( ele[0], type, options);
			return ele;
		default:
			return this.each(function() {
				new $.simulate( this, type, options );
			});
		}
	};
	
	var now = Date.now || function() { return new Date().getTime(); };
	
	var rdocument = /\[object (?:HTML)?Document\]/;
	/**
	 * Tests whether an object is an (HTML) document object.
	 * @param {DOM Element} elem - the object/element to be tested
	 * @returns {Boolean} true if elem is an (HTML) document object.
	 * @private
	 * @author julrich
	 * @since 1.1
	 */
	function isDocument( elem ) {
		return rdocument.test(Object.prototype.toString.call($(elem)[0]));
	}
	
	/**
	 * Selects the first match from an array.
	 * @param {Array} array - Array of objects to be be tested
	 * @param {Function} check - Callback function that accepts one argument (which will be one element
	 * from the array) and returns a boolean.
	 * @returns {Boolean|null} the first element in array for which check returns true.
	 * If none of the elements in array passes check, null is returned.
	 * @private
	 * @author julrich
	 * @since 1.1
	 */
	function selectFirstMatch(array, check) {
		var i;
		if ($.isFunction(check)) {
			for (i=0; i < array.length; i+=1) {
				if (check(array[i])) {
					return array[i];
				}
			}
			return null;
		}
		else {
			for (i=0; i < array.length; i+=1) {
				if (array[i]) {
					return array[i];
				}
			}
			return null;
		}
	}
	
	// Based on the findCenter function from jquery.simulate.js
	/**
	 * Calculates the position of the center of an DOM element.
	 * @param {DOM Element} elem - the element whose center should be calculated.
	 * @returns {Object} an object with the properties x and y
	 * representing the position of the center of elem in page relative coordinates
	 * (i.e. independent of any scrolling).
	 * @private
	 * @author julrich
	 * @since 1.0
	 */
	function findCenter( elem ) {
		var offset;
		elem = $( elem );
		if ( isDocument(elem[0]) ) {
			offset = {left: 0, top: 0}; 
		}
		else {
			offset = elem.offset();
		}
			
		return {
			x: offset.left + elem.outerWidth() / 2,
			y: offset.top + elem.outerHeight() / 2
		};
	}
	
	/**
	 * Converts page relative coordinates into client relative coordinates.
	 * @param {Numeric|Object} x - Either the x coordinate of the page relative coordinates or
	 * an object with the properties pageX and pageY representing page
	 * relative coordinates.
	 * @param {Numeric} [y] - If x is numeric (i.e. the x coordinate of page relative coordinates),
	 * then this is the y coordinate. If x is an object, this parameter is skipped.
	 * @param {DOM Document} [docRel] - Optional DOM document object used to calculate the client relative
	 * coordinates. The page relative coordinates are interpreted as being relative to that document and
	 * the scroll position of that document is used to calculate the client relative coordinates.
	 * By default, document is used.
	 * @returns {Object} an object representing the client relative coordinates corresponding to the
	 * given page relative coordinates. The object either provides the properties x and
	 * y when x and y were given as arguments, or clientX
	 * and clientY when the parameter x was given as an object (see above).
	 * @private
	 * @author julrich
	 * @since 1.0
	 */
	function pageToClientPos(x, y, docRel) {
		var jDocument;
		if ( isDocument(y) ) {
			jDocument = $(y);
		} else {
			jDocument = $(docRel || document);
		}
		
		if (typeof x === "number" && typeof y === "number") {
			return {
				x: x - jDocument.scrollLeft(),
				y: y - jDocument.scrollTop()
			};
		}
		else if (typeof x === "object" && x.pageX && x.pageY) {
			return {
				clientX: x.pageX - jDocument.scrollLeft(),
				clientY: x.pageY - jDocument.scrollTop()
			};
		}
	}
	
	/**
	 * Browser-independent implementation of document.elementFromPoint().
	 * 
	 * When run for the first time on a scrolled page, this function performs a check on how
	 * document.elementFromPoint() is implemented in the current browser. It stores
	 * the results in two static variables so that the check can be skipped for successive calls.
	 * 
	 * @param {Numeric|Object} x - Either the x coordinate of client relative coordinates or an object
	 * with the properties x and y representing client relative coordinates.
	 * @param {Numeric} [y] - If x is numeric (i.e. the x coordinate of client relative coordinates),
	 * this is the y coordinate. If x is an object, this parameter is skipped.
	 * @param {DOM Document} [docRel] - Optional DOM document object
	 * @returns {DOM Element|Null}
	 * @private
	 * @author Nicolas Zeh (Basic idea), julrich
	 * @see http://www.zehnet.de/2010/11/19/document-elementfrompoint-a-jquery-solution/
	 * @since 1.0
	 */
	function elementAtPosition(x, y, docRel) {
		var doc;
		if ( isDocument(y) ) {
			doc = y;
		} else {
			doc = docRel || document;
		}
		
		if(!doc.elementFromPoint) {
			return null;
		}

		var clientX = x, clientY = y;
		if (typeof x === "object" && (x.clientX || x.clientY)) {
			clientX = x.clientX || 0 ;
			clientY = x.clientY || 0;
		}
		
		if(elementAtPosition.prototype.check)
		{
			var sl, ele;
			if ((sl = $(doc).scrollTop()) >0)
			{
				ele = doc.elementFromPoint(0, sl + $(window).height() -1);
				if ( ele !== null && ele.tagName.toUpperCase() === 'HTML' ) { ele = null; }
				elementAtPosition.prototype.nativeUsesRelative = ( ele === null );
			}
			else if((sl = $(doc).scrollLeft()) >0)
			{
				ele = doc.elementFromPoint(sl + $(window).width() -1, 0);
				if ( ele !== null && ele.tagName.toUpperCase() === 'HTML' ) { ele = null; }
				elementAtPosition.prototype.nativeUsesRelative = ( ele === null );
			}
			elementAtPosition.prototype.check = (sl<=0); // Check was not meaningful because we were at scroll position 0
		}

		if(!elementAtPosition.prototype.nativeUsesRelative)
		{
			clientX += $(doc).scrollLeft();
			clientY += $(doc).scrollTop();
		}

		return doc.elementFromPoint(clientX,clientY);
	}
	// Default values for the check variables
	elementAtPosition.prototype.check = true;
	elementAtPosition.prototype.nativeUsesRelative = true;
	
	/**
	 * Informs the rest of the world that the drag is finished.
	 * @param {DOM Element} ele - The element which was dragged.
	 * @param {Object} [options] - The drag options.
	 * @fires simulate-drag
	 * @private
	 * @author julrich 
	 * @since 1.0
	 */
	function dragFinished(ele, options) {
		var opts = options || {};
		$(ele).trigger({type: "simulate-drag"});
		if ($.isFunction(opts.callback)) {
			opts.callback.apply(ele);
		}
	}
	
	/**
	 * Generates a series of mousemove events for a drag.
	 * @param {Object} self - The simulate object.
	 * @param {DOM Element} ele - The element which is dragged.
	 * @param {Object} start - The start coordinates of the drag, represented using the properties
	 * x and y.
	 * @param {Object} drag - The distance to be dragged, represented using the properties dx
	 * and dy.
	 * @param {Object} options - The drag options. Must have the property interpolation
	 * containing the interpolation options (stepWidth, stepCount, etc.).
	 * @requires eventTarget
	 * @requires now()
	 * @private
	 * @author julrich
	 * @since 1.0
	 */
	function interpolatedEvents(self, ele, start, drag, options) {
		var targetDoc = selectFirstMatch([ele, ele.ownerDocument], isDocument) || document,
			interpolOptions = options.interpolation,
			dragDistance = Math.sqrt(Math.pow(drag.dx,2) + Math.pow(drag.dy,2)), // sqrt(a^2 + b^2)
			stepWidth, stepCount, stepVector;
		
		if (interpolOptions.stepWidth) {
			stepWidth = parseInt(interpolOptions.stepWidth, 10);
			stepCount = Math.floor(dragDistance / stepWidth)-1;
			var stepScale = stepWidth / dragDistance;
			stepVector = {x: drag.dx*stepScale, y: drag.dy*stepScale };
		}
		else {
			stepCount = parseInt(interpolOptions.stepCount, 10);
			stepWidth = dragDistance / (stepCount+1);
			stepVector = {x: drag.dx/(stepCount+1), y: drag.dy/(stepCount+1)};
		}
		
		
		var coords = $.extend({},start);
		
		/**
		 * Calculates the effective coordinates for one mousemove event and fires the event.
		 * @requires eventTarget
		 * @requires targetDoc
		 * @requires coords
		 * @requires stepVector
		 * @requires interpolOptions
		 * @fires mousemove
		 * @inner
		 * @author julrich
		 * @since 1.0
		 */
		function interpolationStep() {
			coords.x += stepVector.x;
			coords.y += stepVector.y;
			var effectiveCoords = {pageX: coords.x, pageY: coords.y};
			if (interpolOptions.shaky && (interpolOptions.shaky === true || !isNaN(parseInt(interpolOptions.shaky,10)) )) {
				var amplitude = (interpolOptions.shaky === true)? 1 : parseInt(interpolOptions.shaky,10);
				effectiveCoords.pageX += Math.floor(Math.random()*(2*amplitude+1)-amplitude);
				effectiveCoords.pageY += Math.floor(Math.random()*(2*amplitude+1)-amplitude);
			}
			var clientCoord = pageToClientPos(effectiveCoords, targetDoc),
				eventTarget = elementAtPosition(clientCoord, targetDoc) || ele;
			self.simulateEvent( eventTarget, "mousemove", {pageX: Math.round(effectiveCoords.pageX), pageY: Math.round(effectiveCoords.pageY)});	
		}
		
		
		var lastTime;
		
		/**
		 * Performs one interpolation step (i.e. cares about firing the event) and then sleeps for
		 * stepDelay milliseconds.
		 * @requires lastTime
		 * @requires stepDelay
		 * @requires step
		 * @requires ele
		 * @requires eventTarget
		 * @reuiqre targetDoc
		 * @requires start
		 * @requires drag
		 * @requires now()
		 * @inner
		 * @author julrich
		 * @since 1.0
		 */
		function stepAndSleep() {
			var timeElapsed = now() - lastTime; // Work-around for Firefox & IE "bug": setTimeout can fire before the timeout
			if (timeElapsed >= stepDelay) {
				if (step < stepCount) {
					interpolationStep();
					step += 1;
					lastTime = now();
					setTimeout(stepAndSleep, stepDelay);
				}
				else {
					var pageCoord = {pageX: Math.round(start.x+drag.dx), pageY: Math.round(start.y+drag.dy)},
						clientCoord = pageToClientPos(pageCoord, targetDoc),
						eventTarget = elementAtPosition(clientCoord, targetDoc) || ele;
					self.simulateEvent( eventTarget, "mousemove", pageCoord);
					dragFinished(ele, options);
				}
			}
			else {
				setTimeout(stepAndSleep, stepDelay - timeElapsed);
			}

		}

		if ( (!interpolOptions.stepDelay && !interpolOptions.duration) || ((interpolOptions.stepDelay <= 0) && (interpolOptions.duration <= 0)) ) {
			// Trigger as fast as possible
			for (var i=0; i < stepCount; i+=1) {
				interpolationStep();
			}
			var pageCoord = {pageX: Math.round(start.x+drag.dx), pageY: Math.round(start.y+drag.dy)},
				clientCoord = pageToClientPos(pageCoord, targetDoc),
				eventTarget = elementAtPosition(clientCoord, targetDoc) || ele;
			self.simulateEvent( eventTarget, "mousemove", pageCoord);
			dragFinished(ele, options);
		}
		else {
			var stepDelay = parseInt(interpolOptions.stepDelay,10) || Math.ceil(parseInt(interpolOptions.duration,10) / (stepCount+1));
			var step = 0;

			lastTime = now();
			setTimeout(stepAndSleep, stepDelay);
		}
		
	}

	/**
	 * @returns {Object|undefined} an object containing information about the currently active drag
	 * or undefined when there is no active drag.
	 * The returned object contains the following properties:
	 * 
    *
  • dragElement: the dragged element
  • *
  • dragStart: object with the properties x and y * representing the page relative start coordinates of the drag
  • *
  • dragDistance: object with the properties x and y * representing the distance of the drag in x and y direction
  • *
* @public * @author julrich * @since 1.0 */ $.simulate.activeDrag = function() { if (!$.simulate._activeDrag) { return undefined; } return $.extend(true,{},$.simulate._activeDrag); }; $.extend( $.simulate.prototype, /** * @lends $.simulate.prototype */ { /** * Simulates a drag. * * @see https://github.com/j-ulrich/jquery-simulate-ext/blob/master/doc/drag-n-drop.md * @public * @author julrich * @since 1.0 */ simulateDrag: function() { var self = this, ele = self.target, options = $.extend({ dx: 0, dy: 0, dragTarget: undefined, clickToDrag: false, interpolation: { stepWidth: 0, stepCount: 0, stepDelay: 0, duration: 0, shaky: false }, callback: undefined }, this.options); var start, continueDrag = ($.simulate._activeDrag && $.simulate._activeDrag.dragElement === ele); if (continueDrag) { start = $.simulate._activeDrag.dragStart; } else { start = findCenter( ele ); } var x = Math.round( start.x ), y = Math.round( start.y ), coord = { pageX: x, pageY: y }, dx, dy; if (options.dragTarget) { var end = findCenter(options.dragTarget); dx = Math.round(end.x - start.x); dy = Math.round(end.y - start.y); } else { dx = options.dx || 0; dy = options.dy || 0; } if (continueDrag) { // We just continue to move the dragged element $.simulate._activeDrag.dragDistance.x += dx; $.simulate._activeDrag.dragDistance.y += dy; coord = { pageX: Math.round(x + $.simulate._activeDrag.dragDistance.x) , pageY: Math.round(y + $.simulate._activeDrag.dragDistance.y) }; } else { if ($.simulate._activeDrag) { // Drop before starting a new drag $($.simulate._activeDrag.dragElement).simulate( "drop" ); } // We start a new drag self.simulateEvent( ele, "mousedown", coord ); if (options.clickToDrag === true) { self.simulateEvent( ele, "mouseup", coord ); self.simulateEvent( ele, "click", coord ); } $(ele).add(ele.ownerDocument).one('mouseup', function() { $.simulate._activeDrag = undefined; }); $.extend($.simulate, { _activeDrag: { dragElement: ele, dragStart: { x: x, y: y }, dragDistance: { x: dx, y: dy } } }); coord = { pageX: Math.round(x + dx), pageY: Math.round(y + dy) }; } if (dx !== 0 || dy !== 0) { if ( options.interpolation && (options.interpolation.stepCount || options.interpolation.stepWidth) ) { interpolatedEvents(self, ele, {x: x, y: y}, {dx: dx, dy: dy}, options); } else { var targetDoc = selectFirstMatch([ele, ele.ownerDocument], isDocument) || document, clientCoord = pageToClientPos(coord, targetDoc), eventTarget = elementAtPosition(clientCoord, targetDoc) || ele; self.simulateEvent( eventTarget, "mousemove", coord ); dragFinished(ele, options); } } else { dragFinished(ele, options); } }, /** * Simulates a drop. * * @see https://github.com/j-ulrich/jquery-simulate-ext/blob/master/doc/drag-n-drop.md * @public * @author julrich * @since 1.0 */ simulateDrop: function() { var self = this, ele = this.target, activeDrag = $.simulate._activeDrag, options = $.extend({ clickToDrop: false, callback: undefined }, self.options), moveBeforeDrop = true, center = findCenter( ele ), x = Math.round( center.x ), y = Math.round( center.y ), coord = { pageX: x, pageY: y }, targetDoc = ( (activeDrag)? selectFirstMatch([activeDrag.dragElement, activeDrag.dragElement.ownerDocument], isDocument) : selectFirstMatch([ele, ele.ownerDocument], isDocument) ) || document, clientCoord = pageToClientPos(coord, targetDoc), eventTarget = elementAtPosition(clientCoord, targetDoc); if (activeDrag && (activeDrag.dragElement === ele || isDocument(ele))) { // We already moved the mouse during the drag so we just simulate the drop on the end position x = Math.round(activeDrag.dragStart.x + activeDrag.dragDistance.x); y = Math.round(activeDrag.dragStart.y + activeDrag.dragDistance.y); coord = { pageX: x, pageY: y }; clientCoord = pageToClientPos(coord, targetDoc); eventTarget = elementAtPosition(clientCoord, targetDoc); moveBeforeDrop = false; } if (!eventTarget) { eventTarget = (activeDrag)? activeDrag.dragElement : ele; } if (moveBeforeDrop === true) { // Else we assume the drop should happen on target, so we move there self.simulateEvent( eventTarget, "mousemove", coord ); } if (options.clickToDrop) { self.simulateEvent( eventTarget, "mousedown", coord ); } this.simulateEvent( eventTarget, "mouseup", coord ); if (options.clickToDrop) { self.simulateEvent( eventTarget, "click", coord ); } $.simulate._activeDrag = undefined; $(eventTarget).trigger({type: "simulate-drop"}); if ($.isFunction(options.callback)) { options.callback.apply(eventTarget); } }, /** * Simulates a drag followed by drop. * * @see https://github.com/j-ulrich/jquery-simulate-ext/blob/master/doc/drag-n-drop.md * @public * @author julrich * @since 1.0 */ simulateDragNDrop: function() { var self = this, ele = this.target, options = $.extend({ dragTarget: undefined, dropTarget: undefined }, self.options), // If there is a dragTarget or dx/dy, then we drag there and simulate an independent drop on dropTarget or ele dropEle = ((options.dragTarget || options.dx || options.dy)? options.dropTarget : ele) || ele; /* dx = (options.dropTarget)? 0 : (options.dx || 0), dy = (options.dropTarget)? 0 : (options.dy || 0), dragDistance = { dx: dx, dy: dy }; $.extend(options, dragDistance); */ $(ele).simulate( "drag", $.extend({},options,{ // If there is no dragTarget, no dx and no dy, we drag onto the dropTarget directly dragTarget: options.dragTarget || ((options.dx || options.dy)?undefined:options.dropTarget), callback: function() { $(dropEle).simulate( "drop", options ); } })); } }); }(jQuery));




© 2015 - 2025 Weber Informatics LLC | Privacy Policy