591 lines
29 KiB
JavaScript
591 lines
29 KiB
JavaScript
/**
|
|
* angular-drag-and-drop-lists v1.4.0
|
|
*
|
|
* Copyright (c) 2014 Marcel Juenemann marcel@juenemann.cc
|
|
* Copyright (c) 2014-2016 Google Inc.
|
|
* https://github.com/marceljuenemann/angular-drag-and-drop-lists
|
|
*
|
|
* License: MIT
|
|
*/
|
|
angular.module('dndLists', [])
|
|
|
|
/**
|
|
* Use the dnd-draggable attribute to make your element draggable
|
|
*
|
|
* Attributes:
|
|
* - dnd-draggable Required attribute. The value has to be an object that represents the data
|
|
* of the element. In case of a drag and drop operation the object will be
|
|
* serialized and unserialized on the receiving end.
|
|
* - dnd-selected Callback that is invoked when the element was clicked but not dragged.
|
|
* The original click event will be provided in the local event variable.
|
|
* - dnd-effect-allowed Use this attribute to limit the operations that can be performed. Options:
|
|
* - "move": The drag operation will move the element. This is the default.
|
|
* - "copy": The drag operation will copy the element. Shows a copy cursor.
|
|
* - "copyMove": The user can choose between copy and move by pressing the
|
|
* ctrl or shift key. *Not supported in IE:* In Internet Explorer this
|
|
* option will be the same as "copy". *Not fully supported in Chrome on
|
|
* Windows:* In the Windows version of Chrome the cursor will always be the
|
|
* move cursor. However, when the user drops an element and has the ctrl
|
|
* key pressed, we will perform a copy anyways.
|
|
* - HTML5 also specifies the "link" option, but this library does not
|
|
* actively support it yet, so use it at your own risk.
|
|
* - dnd-moved Callback that is invoked when the element was moved. Usually you will
|
|
* remove your element from the original list in this callback, since the
|
|
* directive is not doing that for you automatically. The original dragend
|
|
* event will be provided in the local event variable.
|
|
* - dnd-canceled Callback that is invoked if the element was dragged, but the operation was
|
|
* canceled and the element was not dropped. The original dragend event will
|
|
* be provided in the local event variable.
|
|
* - dnd-copied Same as dnd-moved, just that it is called when the element was copied
|
|
* instead of moved. The original dragend event will be provided in the local
|
|
* event variable.
|
|
* - dnd-dragstart Callback that is invoked when the element was dragged. The original
|
|
* dragstart event will be provided in the local event variable.
|
|
* - dnd-dragend Callback that is invoked when the drag operation ended. Available local
|
|
* variables are event and dropEffect.
|
|
* - dnd-type Use this attribute if you have different kinds of items in your
|
|
* application and you want to limit which items can be dropped into which
|
|
* lists. Combine with dnd-allowed-types on the dnd-list(s). This attribute
|
|
* should evaluate to a string, although this restriction is not enforced.
|
|
* - dnd-disable-if You can use this attribute to dynamically disable the draggability of the
|
|
* element. This is useful if you have certain list items that you don't want
|
|
* to be draggable, or if you want to disable drag & drop completely without
|
|
* having two different code branches (e.g. only allow for admins).
|
|
* **Note**: If your element is not draggable, the user is probably able to
|
|
* select text or images inside of it. Since a selection is always draggable,
|
|
* this breaks your UI. You most likely want to disable user selection via
|
|
* CSS (see user-select).
|
|
*
|
|
* CSS classes:
|
|
* - dndDragging This class will be added to the element while the element is being
|
|
* dragged. It will affect both the element you see while dragging and the
|
|
* source element that stays at it's position. Do not try to hide the source
|
|
* element with this class, because that will abort the drag operation.
|
|
* - dndDraggingSource This class will be added to the element after the drag operation was
|
|
* started, meaning it only affects the original element that is still at
|
|
* it's source position, and not the "element" that the user is dragging with
|
|
* his mouse pointer.
|
|
*/
|
|
.directive('dndDraggable', ['$parse', '$timeout', 'dndDropEffectWorkaround', 'dndDragTypeWorkaround',
|
|
function($parse, $timeout, dndDropEffectWorkaround, dndDragTypeWorkaround) {
|
|
return function(scope, element, attr) {
|
|
// Set the HTML5 draggable attribute on the element
|
|
element.attr("draggable", "true");
|
|
|
|
// If the dnd-disable-if attribute is set, we have to watch that
|
|
if (attr.dndDisableIf) {
|
|
scope.$watch(attr.dndDisableIf, function(disabled) {
|
|
element.attr("draggable", !disabled);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* When the drag operation is started we have to prepare the dataTransfer object,
|
|
* which is the primary way we communicate with the target element
|
|
*/
|
|
element.on('dragstart', function(event) {
|
|
event = event.originalEvent || event;
|
|
|
|
// Check whether the element is draggable, since dragstart might be triggered on a child.
|
|
if (element.attr('draggable') == 'false') return true;
|
|
|
|
// Serialize the data associated with this element. IE only supports the Text drag type
|
|
event.dataTransfer.setData("Text", angular.toJson(scope.$eval(attr.dndDraggable)));
|
|
|
|
// Only allow actions specified in dnd-effect-allowed attribute
|
|
event.dataTransfer.effectAllowed = attr.dndEffectAllowed || "move";
|
|
|
|
// Add CSS classes. See documentation above
|
|
element.addClass("dndDragging");
|
|
$timeout(function() { element.addClass("dndDraggingSource"); }, 0);
|
|
|
|
// Workarounds for stupid browsers, see description below
|
|
dndDropEffectWorkaround.dropEffect = "none";
|
|
dndDragTypeWorkaround.isDragging = true;
|
|
|
|
// Save type of item in global state. Usually, this would go into the dataTransfer
|
|
// typename, but we have to use "Text" there to support IE
|
|
dndDragTypeWorkaround.dragType = attr.dndType ? scope.$eval(attr.dndType) : undefined;
|
|
|
|
// Try setting a proper drag image if triggered on a dnd-handle (won't work in IE).
|
|
if (event._dndHandle && event.dataTransfer.setDragImage) {
|
|
event.dataTransfer.setDragImage(element[0], 0, 0);
|
|
}
|
|
|
|
// Invoke callback
|
|
$parse(attr.dndDragstart)(scope, {event: event});
|
|
|
|
event.stopPropagation();
|
|
});
|
|
|
|
/**
|
|
* The dragend event is triggered when the element was dropped or when the drag
|
|
* operation was aborted (e.g. hit escape button). Depending on the executed action
|
|
* we will invoke the callbacks specified with the dnd-moved or dnd-copied attribute.
|
|
*/
|
|
element.on('dragend', function(event) {
|
|
event = event.originalEvent || event;
|
|
|
|
// Invoke callbacks. Usually we would use event.dataTransfer.dropEffect to determine
|
|
// the used effect, but Chrome has not implemented that field correctly. On Windows
|
|
// it always sets it to 'none', while Chrome on Linux sometimes sets it to something
|
|
// else when it's supposed to send 'none' (drag operation aborted).
|
|
var dropEffect = dndDropEffectWorkaround.dropEffect;
|
|
scope.$apply(function() {
|
|
switch (dropEffect) {
|
|
case "move":
|
|
$parse(attr.dndMoved)(scope, {event: event});
|
|
break;
|
|
case "copy":
|
|
$parse(attr.dndCopied)(scope, {event: event});
|
|
break;
|
|
case "none":
|
|
$parse(attr.dndCanceled)(scope, {event: event});
|
|
break;
|
|
}
|
|
$parse(attr.dndDragend)(scope, {event: event, dropEffect: dropEffect});
|
|
});
|
|
|
|
// Clean up
|
|
element.removeClass("dndDragging");
|
|
$timeout(function() { element.removeClass("dndDraggingSource"); }, 0);
|
|
dndDragTypeWorkaround.isDragging = false;
|
|
event.stopPropagation();
|
|
});
|
|
|
|
/**
|
|
* When the element is clicked we invoke the callback function
|
|
* specified with the dnd-selected attribute.
|
|
*/
|
|
element.on('click', function(event) {
|
|
if (!attr.dndSelected) return;
|
|
|
|
event = event.originalEvent || event;
|
|
scope.$apply(function() {
|
|
$parse(attr.dndSelected)(scope, {event: event});
|
|
});
|
|
|
|
// Prevent triggering dndSelected in parent elements.
|
|
event.stopPropagation();
|
|
});
|
|
|
|
/**
|
|
* Workaround to make element draggable in IE9
|
|
*/
|
|
element.on('selectstart', function() {
|
|
if (this.dragDrop) this.dragDrop();
|
|
});
|
|
};
|
|
}])
|
|
|
|
/**
|
|
* Use the dnd-list attribute to make your list element a dropzone. Usually you will add a single
|
|
* li element as child with the ng-repeat directive. If you don't do that, we will not be able to
|
|
* position the dropped element correctly. If you want your list to be sortable, also add the
|
|
* dnd-draggable directive to your li element(s). Both the dnd-list and it's direct children must
|
|
* have position: relative CSS style, otherwise the positioning algorithm will not be able to
|
|
* determine the correct placeholder position in all browsers.
|
|
*
|
|
* Attributes:
|
|
* - dnd-list Required attribute. The value has to be the array in which the data of
|
|
* the dropped element should be inserted.
|
|
* - dnd-allowed-types Optional array of allowed item types. When used, only items that had a
|
|
* matching dnd-type attribute will be dropable.
|
|
* - dnd-disable-if Optional boolean expresssion. When it evaluates to true, no dropping
|
|
* into the list is possible. Note that this also disables rearranging
|
|
* items inside the list.
|
|
* - dnd-horizontal-list Optional boolean expresssion. When it evaluates to true, the positioning
|
|
* algorithm will use the left and right halfs of the list items instead of
|
|
* the upper and lower halfs.
|
|
* - dnd-dragover Optional expression that is invoked when an element is dragged over the
|
|
* list. If the expression is set, but does not return true, the element is
|
|
* not allowed to be dropped. The following variables will be available:
|
|
* - event: The original dragover event sent by the browser.
|
|
* - index: The position in the list at which the element would be dropped.
|
|
* - type: The dnd-type set on the dnd-draggable, or undefined if unset.
|
|
* - external: Whether the element was dragged from an external source.
|
|
* - dnd-drop Optional expression that is invoked when an element is dropped on the
|
|
* list. The following variables will be available:
|
|
* - event: The original drop event sent by the browser.
|
|
* - index: The position in the list at which the element would be dropped.
|
|
* - item: The transferred object.
|
|
* - type: The dnd-type set on the dnd-draggable, or undefined if unset.
|
|
* - external: Whether the element was dragged from an external source.
|
|
* The return value determines the further handling of the drop:
|
|
* - false: The drop will be canceled and the element won't be inserted.
|
|
* - true: Signalises that the drop is allowed, but the dnd-drop
|
|
* callback already took care of inserting the element.
|
|
* - otherwise: All other return values will be treated as the object to
|
|
* insert into the array. In most cases you want to simply return the
|
|
* item parameter, but there are no restrictions on what you can return.
|
|
* - dnd-inserted Optional expression that is invoked after a drop if the element was
|
|
* actually inserted into the list. The same local variables as for
|
|
* dnd-drop will be available. Note that for reorderings inside the same
|
|
* list the old element will still be in the list due to the fact that
|
|
* dnd-moved was not called yet.
|
|
* - dnd-external-sources Optional boolean expression. When it evaluates to true, the list accepts
|
|
* drops from sources outside of the current browser tab. This allows to
|
|
* drag and drop accross different browser tabs. Note that this will allow
|
|
* to drop arbitrary text into the list, thus it is highly recommended to
|
|
* implement the dnd-drop callback to check the incoming element for
|
|
* sanity. Furthermore, the dnd-type of external sources can not be
|
|
* determined, therefore do not rely on restrictions of dnd-allowed-type.
|
|
*
|
|
* CSS classes:
|
|
* - dndPlaceholder When an element is dragged over the list, a new placeholder child
|
|
* element will be added. This element is of type li and has the class
|
|
* dndPlaceholder set. Alternatively, you can define your own placeholder
|
|
* by creating a child element with dndPlaceholder class.
|
|
* - dndDragover Will be added to the list while an element is dragged over the list.
|
|
*/
|
|
.directive('dndList', ['$parse', '$timeout', 'dndDropEffectWorkaround', 'dndDragTypeWorkaround',
|
|
function($parse, $timeout, dndDropEffectWorkaround, dndDragTypeWorkaround) {
|
|
return function(scope, element, attr) {
|
|
// While an element is dragged over the list, this placeholder element is inserted
|
|
// at the location where the element would be inserted after dropping
|
|
var placeholder = getPlaceholderElement();
|
|
var placeholderNode = placeholder[0];
|
|
var listNode = element[0];
|
|
placeholder.remove();
|
|
|
|
var horizontal = attr.dndHorizontalList && scope.$eval(attr.dndHorizontalList);
|
|
var externalSources = attr.dndExternalSources && scope.$eval(attr.dndExternalSources);
|
|
|
|
/**
|
|
* The dragenter event is fired when a dragged element or text selection enters a valid drop
|
|
* target. According to the spec, we either need to have a dropzone attribute or listen on
|
|
* dragenter events and call preventDefault(). It should be noted though that no browser seems
|
|
* to enforce this behaviour.
|
|
*/
|
|
element.on('dragenter', function (event) {
|
|
event = event.originalEvent || event;
|
|
if (!isDropAllowed(event)) return true;
|
|
event.preventDefault();
|
|
});
|
|
|
|
/**
|
|
* The dragover event is triggered "every few hundred milliseconds" while an element
|
|
* is being dragged over our list, or over an child element.
|
|
*/
|
|
element.on('dragover', function(event) {
|
|
event = event.originalEvent || event;
|
|
|
|
if (!isDropAllowed(event)) return true;
|
|
|
|
// First of all, make sure that the placeholder is shown
|
|
// This is especially important if the list is empty
|
|
if (placeholderNode.parentNode != listNode) {
|
|
element.append(placeholder);
|
|
}
|
|
|
|
if (event.target !== listNode) {
|
|
// Try to find the node direct directly below the list node.
|
|
var listItemNode = event.target;
|
|
while (listItemNode.parentNode !== listNode && listItemNode.parentNode) {
|
|
listItemNode = listItemNode.parentNode;
|
|
}
|
|
|
|
if (listItemNode.parentNode === listNode && listItemNode !== placeholderNode) {
|
|
// If the mouse pointer is in the upper half of the child element,
|
|
// we place it before the child element, otherwise below it.
|
|
if (isMouseInFirstHalf(event, listItemNode)) {
|
|
listNode.insertBefore(placeholderNode, listItemNode);
|
|
} else {
|
|
listNode.insertBefore(placeholderNode, listItemNode.nextSibling);
|
|
}
|
|
}
|
|
} else {
|
|
// This branch is reached when we are dragging directly over the list element.
|
|
// Usually we wouldn't need to do anything here, but the IE does not fire it's
|
|
// events for the child element, only for the list directly. Therefore, we repeat
|
|
// the positioning algorithm for IE here.
|
|
if (isMouseInFirstHalf(event, placeholderNode, true)) {
|
|
// Check if we should move the placeholder element one spot towards the top.
|
|
// Note that display none elements will have offsetTop and offsetHeight set to
|
|
// zero, therefore we need a special check for them.
|
|
while (placeholderNode.previousElementSibling
|
|
&& (isMouseInFirstHalf(event, placeholderNode.previousElementSibling, true)
|
|
|| placeholderNode.previousElementSibling.offsetHeight === 0)) {
|
|
listNode.insertBefore(placeholderNode, placeholderNode.previousElementSibling);
|
|
}
|
|
} else {
|
|
// Check if we should move the placeholder element one spot towards the bottom
|
|
while (placeholderNode.nextElementSibling &&
|
|
!isMouseInFirstHalf(event, placeholderNode.nextElementSibling, true)) {
|
|
listNode.insertBefore(placeholderNode,
|
|
placeholderNode.nextElementSibling.nextElementSibling);
|
|
}
|
|
}
|
|
}
|
|
|
|
// At this point we invoke the callback, which still can disallow the drop.
|
|
// We can't do this earlier because we want to pass the index of the placeholder.
|
|
if (attr.dndDragover && !invokeCallback(attr.dndDragover, event, getPlaceholderIndex())) {
|
|
return stopDragover();
|
|
}
|
|
|
|
element.addClass("dndDragover");
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
return false;
|
|
});
|
|
|
|
/**
|
|
* When the element is dropped, we use the position of the placeholder element as the
|
|
* position where we insert the transferred data. This assumes that the list has exactly
|
|
* one child element per array element.
|
|
*/
|
|
element.on('drop', function(event) {
|
|
event = event.originalEvent || event;
|
|
|
|
if (!isDropAllowed(event)) return true;
|
|
|
|
// The default behavior in Firefox is to interpret the dropped element as URL and
|
|
// forward to it. We want to prevent that even if our drop is aborted.
|
|
event.preventDefault();
|
|
|
|
// Unserialize the data that was serialized in dragstart. According to the HTML5 specs,
|
|
// the "Text" drag type will be converted to text/plain, but IE does not do that.
|
|
var data = event.dataTransfer.getData("Text") || event.dataTransfer.getData("text/plain");
|
|
var transferredObject;
|
|
try {
|
|
transferredObject = JSON.parse(data);
|
|
} catch(e) {
|
|
return stopDragover();
|
|
}
|
|
|
|
// Invoke the callback, which can transform the transferredObject and even abort the drop.
|
|
var index = getPlaceholderIndex();
|
|
if (attr.dndDrop) {
|
|
transferredObject = invokeCallback(attr.dndDrop, event, index, transferredObject);
|
|
if (!transferredObject) {
|
|
return stopDragover();
|
|
}
|
|
}
|
|
|
|
// Insert the object into the array, unless dnd-drop took care of that (returned true).
|
|
if (transferredObject !== true) {
|
|
scope.$apply(function() {
|
|
scope.$eval(attr.dndList).splice(index, 0, transferredObject);
|
|
});
|
|
}
|
|
invokeCallback(attr.dndInserted, event, index, transferredObject);
|
|
|
|
// In Chrome on Windows the dropEffect will always be none...
|
|
// We have to determine the actual effect manually from the allowed effects
|
|
if (event.dataTransfer.dropEffect === "none") {
|
|
if (event.dataTransfer.effectAllowed === "copy" ||
|
|
event.dataTransfer.effectAllowed === "move") {
|
|
dndDropEffectWorkaround.dropEffect = event.dataTransfer.effectAllowed;
|
|
} else {
|
|
dndDropEffectWorkaround.dropEffect = event.ctrlKey ? "copy" : "move";
|
|
}
|
|
} else {
|
|
dndDropEffectWorkaround.dropEffect = event.dataTransfer.dropEffect;
|
|
}
|
|
|
|
// Clean up
|
|
stopDragover();
|
|
event.stopPropagation();
|
|
return false;
|
|
});
|
|
|
|
/**
|
|
* We have to remove the placeholder when the element is no longer dragged over our list. The
|
|
* problem is that the dragleave event is not only fired when the element leaves our list,
|
|
* but also when it leaves a child element -- so practically it's fired all the time. As a
|
|
* workaround we wait a few milliseconds and then check if the dndDragover class was added
|
|
* again. If it is there, dragover must have been called in the meantime, i.e. the element
|
|
* is still dragging over the list. If you know a better way of doing this, please tell me!
|
|
*/
|
|
element.on('dragleave', function(event) {
|
|
event = event.originalEvent || event;
|
|
|
|
element.removeClass("dndDragover");
|
|
$timeout(function() {
|
|
if (!element.hasClass("dndDragover")) {
|
|
placeholder.remove();
|
|
}
|
|
}, 100);
|
|
});
|
|
|
|
/**
|
|
* Checks whether the mouse pointer is in the first half of the given target element.
|
|
*
|
|
* In Chrome we can just use offsetY, but in Firefox we have to use layerY, which only
|
|
* works if the child element has position relative. In IE the events are only triggered
|
|
* on the listNode instead of the listNodeItem, therefore the mouse positions are
|
|
* relative to the parent element of targetNode.
|
|
*/
|
|
function isMouseInFirstHalf(event, targetNode, relativeToParent) {
|
|
var mousePointer = horizontal ? (event.offsetX || event.layerX)
|
|
: (event.offsetY || event.layerY);
|
|
var targetSize = horizontal ? targetNode.offsetWidth : targetNode.offsetHeight;
|
|
var targetPosition = horizontal ? targetNode.offsetLeft : targetNode.offsetTop;
|
|
targetPosition = relativeToParent ? targetPosition : 0;
|
|
return mousePointer < targetPosition + targetSize / 2;
|
|
}
|
|
|
|
/**
|
|
* Tries to find a child element that has the dndPlaceholder class set. If none was found, a
|
|
* new li element is created.
|
|
*/
|
|
function getPlaceholderElement() {
|
|
var placeholder;
|
|
angular.forEach(element.children(), function(childNode) {
|
|
var child = angular.element(childNode);
|
|
if (child.hasClass('dndPlaceholder')) {
|
|
placeholder = child;
|
|
}
|
|
});
|
|
return placeholder || angular.element("<li class='dndPlaceholder'></li>");
|
|
}
|
|
|
|
/**
|
|
* We use the position of the placeholder node to determine at which position of the array the
|
|
* object needs to be inserted
|
|
*/
|
|
function getPlaceholderIndex() {
|
|
return Array.prototype.indexOf.call(listNode.children, placeholderNode);
|
|
}
|
|
|
|
/**
|
|
* Checks various conditions that must be fulfilled for a drop to be allowed
|
|
*/
|
|
function isDropAllowed(event) {
|
|
// Disallow drop from external source unless it's allowed explicitly.
|
|
if (!dndDragTypeWorkaround.isDragging && !externalSources) return false;
|
|
|
|
// Check mimetype. Usually we would use a custom drag type instead of Text, but IE doesn't
|
|
// support that.
|
|
if (!hasTextMimetype(event.dataTransfer.types)) return false;
|
|
|
|
// Now check the dnd-allowed-types against the type of the incoming element. For drops from
|
|
// external sources we don't know the type, so it will need to be checked via dnd-drop.
|
|
if (attr.dndAllowedTypes && dndDragTypeWorkaround.isDragging) {
|
|
var allowed = scope.$eval(attr.dndAllowedTypes);
|
|
if (angular.isArray(allowed) && allowed.indexOf(dndDragTypeWorkaround.dragType) === -1) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Check whether droping is disabled completely
|
|
if (attr.dndDisableIf && scope.$eval(attr.dndDisableIf)) return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Small helper function that cleans up if we aborted a drop.
|
|
*/
|
|
function stopDragover() {
|
|
placeholder.remove();
|
|
element.removeClass("dndDragover");
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Invokes a callback with some interesting parameters and returns the callbacks return value.
|
|
*/
|
|
function invokeCallback(expression, event, index, item) {
|
|
return $parse(expression)(scope, {
|
|
event: event,
|
|
index: index,
|
|
item: item || undefined,
|
|
external: !dndDragTypeWorkaround.isDragging,
|
|
type: dndDragTypeWorkaround.isDragging ? dndDragTypeWorkaround.dragType : undefined
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Check if the dataTransfer object contains a drag type that we can handle. In old versions
|
|
* of IE the types collection will not even be there, so we just assume a drop is possible.
|
|
*/
|
|
function hasTextMimetype(types) {
|
|
if (!types) return true;
|
|
for (var i = 0; i < types.length; i++) {
|
|
if (types[i] === "Text" || types[i] === "text/plain") return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
};
|
|
}])
|
|
|
|
/**
|
|
* Use the dnd-nodrag attribute inside of dnd-draggable elements to prevent them from starting
|
|
* drag operations. This is especially useful if you want to use input elements inside of
|
|
* dnd-draggable elements or create specific handle elements. Note: This directive does not work
|
|
* in Internet Explorer 9.
|
|
*/
|
|
.directive('dndNodrag', function() {
|
|
return function(scope, element, attr) {
|
|
// Set as draggable so that we can cancel the events explicitly
|
|
element.attr("draggable", "true");
|
|
|
|
/**
|
|
* Since the element is draggable, the browser's default operation is to drag it on dragstart.
|
|
* We will prevent that and also stop the event from bubbling up.
|
|
*/
|
|
element.on('dragstart', function(event) {
|
|
event = event.originalEvent || event;
|
|
|
|
if (!event._dndHandle) {
|
|
// If a child element already reacted to dragstart and set a dataTransfer object, we will
|
|
// allow that. For example, this is the case for user selections inside of input elements.
|
|
if (!(event.dataTransfer.types && event.dataTransfer.types.length)) {
|
|
event.preventDefault();
|
|
}
|
|
event.stopPropagation();
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Stop propagation of dragend events, otherwise dnd-moved might be triggered and the element
|
|
* would be removed.
|
|
*/
|
|
element.on('dragend', function(event) {
|
|
event = event.originalEvent || event;
|
|
if (!event._dndHandle) {
|
|
event.stopPropagation();
|
|
}
|
|
});
|
|
};
|
|
})
|
|
|
|
/**
|
|
* Use the dnd-handle directive within a dnd-nodrag element in order to allow dragging with that
|
|
* element after all. Therefore, by combining dnd-nodrag and dnd-handle you can allow
|
|
* dnd-draggable elements to only be dragged via specific "handle" elements. Note that Internet
|
|
* Explorer will show the handle element as drag image instead of the dnd-draggable element. You
|
|
* can work around this by styling the handle element differently when it is being dragged. Use
|
|
* the CSS selector .dndDragging:not(.dndDraggingSource) [dnd-handle] for that.
|
|
*/
|
|
.directive('dndHandle', function() {
|
|
return function(scope, element, attr) {
|
|
element.attr("draggable", "true");
|
|
|
|
element.on('dragstart dragend', function(event) {
|
|
event = event.originalEvent || event;
|
|
event._dndHandle = true;
|
|
});
|
|
};
|
|
})
|
|
|
|
/**
|
|
* This workaround handles the fact that Internet Explorer does not support drag types other than
|
|
* "Text" and "URL". That means we can not know whether the data comes from one of our elements or
|
|
* is just some other data like a text selection. As a workaround we save the isDragging flag in
|
|
* here. When a dropover event occurs, we only allow the drop if we are already dragging, because
|
|
* that means the element is ours.
|
|
*/
|
|
.factory('dndDragTypeWorkaround', function(){ return {} })
|
|
|
|
/**
|
|
* Chrome on Windows does not set the dropEffect field, which we need in dragend to determine
|
|
* whether a drag operation was successful. Therefore we have to maintain it in this global
|
|
* variable. The bug report for that has been open for years:
|
|
* https://code.google.com/p/chromium/issues/detail?id=39399
|
|
*/
|
|
.factory('dndDropEffectWorkaround', function(){ return {} });
|