We use cookies to make our website more effective. By using our website you agree to our privacy policy.

Source: dragdrop.js

/**
 * dragdrop.js is part of Aloha Editor project http://www.alohaeditor.org
 *
 * Aloha Editor ● JavaScript Content Editing Library
 * Copyright (c) 2010-2015 Gentics Software GmbH, Vienna, Austria.
 * Contributors http://www.alohaeditor.org/docs/contributing.html
 *
 * @see
 * http://www.whatwg.org/specs/web-apps/current-work/#dnd
 * http://www.html5rocks.com/en/tutorials/dnd/basics/
 * https://developer.mozilla.org/en-US/docs/Drag_and_drop_events
 * https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer
 * @namespace dragdrop
 */
define([
	'dom',
	'maps',
	'events',
	'editing',
	'boundaries',
	'selections'
], function (
	Dom,
	Maps,
	Events,
	Editing,
	Boundaries,
	Selections
) {
	'use strict';

	/**
	 * The pixel distance between the mouse pointer and where the caret should
	 * be rendered when dragging.
	 *
	 * @const
	 * @type {number}
	 */
	var DRAGGING_CARET_OFFSET = -10;

	/**
	 * Default drag and drop context properites.
	 *
	 * These are the default attributes from which drag and drop contexts will
	 * be created.
	 *
	 * @const
	 * @type {Object.<string, *>}
	 */
	var DEFAULTS = {
		'dropEffect' : 'none',
		'element'    : null,
		'target'     : null,
		'data'       : ['text/plain', '']
	};

	/**
	 * Creates a new drag and drop context.
	 *
	 * The following attributes are supported in the options object that is
	 * passed to this function:
	 *
	 *	`dropEffect`
	 *		The dropEffect attribute controls the drag-and-drop feedback that
	 *		the user is given during a drag-and-drop operation. If the
	 *		`dropEffect` value is set to "copy", for example, the user agent may
	 *		rendered the drag icon with a "+" (plus) sign. The supported values
	 *		are "none", "copy", "link", or "move". All other values are ignored.
	 *
	 *	`element`
	 *		The element on which dragging was initiated on. If the drag and drop
	 *		operation is a moving operation, this element will be relocated into
	 *		the boundary at the point at which the drop event is fired. If the
	 *		drag and drop operation is a copying operation, then this attribute
	 *		should a reference to a deep clone of the element on which dragging
	 *		was initiated.
	 *
	 *	`data`
	 *		A tuple describing the data that will be set to the drag data store.
	 *		See:
	 *		http://www.whatwg.org/specs/web-apps/current-work/multipage/dnd.html#drag-data-store
	 *
	 * @param  {Object} options
	 * @return {Object}
	 * @memberOf dragdrop
	 */
	function Context(options) {
		return Maps.merge({}, DEFAULTS, options);
	}

	/**
	 * Whether or not the given node is draggable.
	 *
	 * In an attempt to follow the implementation on most browsers, text
	 * selections, IMG elements, and anchor elements with an href attribute are
	 * draggable by default.
	 *
	 * @param  {Element} node
	 * @return {boolean}
	 * @memberOf dragdrop
	 */
	function isDraggable(node) {
		if (!Dom.isElementNode(node)) {
			return false;
		}
		var attr = node.getAttribute('draggable');
		if ('false' === attr) {
			return false;
		}
		if ('true' === attr) {
			return true;
		}
		if ('IMG' === node.nodeName) {
			return true;
		}
		return ('A' === node.nodeName) && node.getAttribute('href');
	}

	/**
	 * Moves the given node into the given boundary positions.
	 *
	 * @private
	 * @param  {Boundary} start
	 * @param  {Boundary} end
	 * @param  {Node}     node
	 * @return {Array.<Boundary>}
	 */
	function moveNode(start, end, node) {
		var prev = node.previousSibling;
		var boundary = Editing.insert(start, end, node);
		if (prev && prev.nextSibling) {
			Dom.merge(prev, prev.nextSibling);
		}
		return [boundary, boundary];
	}

	function handleDragStart(event) {
		// Because this is required in Firefox for dragging to start on elements
		// other than IMG elements or anchor elements with href values
		event.nativeEvent.dataTransfer.setData(
			event.dnd.data[0],
			event.dnd.data[1]
		);
		event.dnd.element = event.nativeEvent.target;
		event.dnd.target = event.nativeEvent.target;
	}

	function calculateBoundaries(x, y, doc) {
		var carets = Selections.hideCarets(doc);
		var boundary = Boundaries.fromPosition(
			Dom.scrollLeft(doc) + x,
			Dom.scrollTop(doc) + y,
			doc
		);
		Selections.unhideCarets(carets);
		return [boundary, boundary];
	}

	function handleDragOver(event) {
		var nativeEvent = event.nativeEvent;
		event.selection.boundaries = calculateBoundaries(
			nativeEvent.clientX + DRAGGING_CARET_OFFSET,
			nativeEvent.clientY + DRAGGING_CARET_OFFSET,
			nativeEvent.target.ownerDocument
		);
		// Because this is necessary for dropping to work
		Events.preventDefault(nativeEvent);
	}

	function handleDrop(event) {
		var nativeEvent = event.nativeEvent;
		event.selection.boundaries = calculateBoundaries(
			// +8 because, for some reason the boundaries are always calculated
			// a character behind of where it should be...
			nativeEvent.clientX + DRAGGING_CARET_OFFSET + 8,
			nativeEvent.clientY + DRAGGING_CARET_OFFSET,
			nativeEvent.target.ownerDocument
		);
		if (event.selection.boundaries) {
			event.selection.boundaries = moveNode(
				event.selection.boundaries[0],
				event.selection.boundaries[1],
				event.dnd.element
			);
		}
		Events.stopPropagation(nativeEvent);
		// Because some browsers will otherwise redirect
		Events.preventDefault(nativeEvent);
	}

	var handlers = {
		'dragstart' : handleDragStart,
		'dragover'  : handleDragOver,
		'drop'      : handleDrop
	};

	/**
	 * Processes drag and drop events.
	 *
	 * Updates dnd and nativeEvent
	 *
	 * @param  {AlohaEvent} event
	 * @return {AlohaEvent}
	 * @memberOf dragdrop
	 */
	function middleware(event) {
		if (event.dnd && handlers[event.type]) {
			handlers[event.type](event);
		}
		return event;
	}

	return {
		middleware  : middleware,
		Context     : Context,
		isDraggable : isDraggable
	};
});
comments powered by Disqus