2 * angular-drag-and-drop-lists v2.1.0
4 * Copyright (c) 2014 Marcel Juenemann marcel@juenemann.cc
5 * Copyright (c) 2014-2017 Google Inc.
6 * https://github.com/marceljuenemann/angular-drag-and-drop-lists
12 // In standard-compliant browsers we use a custom mime type and also encode the dnd-type in it.
13 // However, IE and Edge only support a limited number of mime types. The workarounds are described
14 // in https://github.com/marceljuenemann/angular-drag-and-drop-lists/wiki/Data-Transfer-Design
15 var MIME_TYPE = 'application/x-dnd';
16 var EDGE_MIME_TYPE = 'application/json';
17 var MSIE_MIME_TYPE = 'Text';
19 // All valid HTML5 drop effects, in the order in which we prefer to use them.
20 var ALL_EFFECTS = ['move', 'copy', 'link'];
23 * Use the dnd-draggable attribute to make your element draggable
26 * - dnd-draggable Required attribute. The value has to be an object that represents the data
27 * of the element. In case of a drag and drop operation the object will be
28 * serialized and unserialized on the receiving end.
29 * - dnd-effect-allowed Use this attribute to limit the operations that can be performed. Valid
30 * options are "move", "copy" and "link", as well as "all", "copyMove",
31 * "copyLink" and "linkMove". The semantics of these operations are up to you
32 * and have to be implemented using the callbacks described below. If you
33 * allow multiple options, the user can choose between them by using the
34 * modifier keys (OS specific). The cursor will be changed accordingly,
35 * expect for IE and Edge, where this is not supported.
36 * - dnd-type Use this attribute if you have different kinds of items in your
37 * application and you want to limit which items can be dropped into which
38 * lists. Combine with dnd-allowed-types on the dnd-list(s). This attribute
39 * must be a lower case string. Upper case characters can be used, but will
40 * be converted to lower case automatically.
41 * - dnd-disable-if You can use this attribute to dynamically disable the draggability of the
42 * element. This is useful if you have certain list items that you don't want
43 * to be draggable, or if you want to disable drag & drop completely without
44 * having two different code branches (e.g. only allow for admins).
47 * - dnd-dragstart Callback that is invoked when the element was dragged. The original
48 * dragstart event will be provided in the local event variable.
49 * - dnd-moved Callback that is invoked when the element was moved. Usually you will
50 * remove your element from the original list in this callback, since the
51 * directive is not doing that for you automatically. The original dragend
52 * event will be provided in the local event variable.
53 * - dnd-copied Same as dnd-moved, just that it is called when the element was copied
54 * instead of moved, so you probably want to implement a different logic.
55 * - dnd-linked Same as dnd-moved, just that it is called when the element was linked
56 * instead of moved, so you probably want to implement a different logic.
57 * - dnd-canceled Callback that is invoked if the element was dragged, but the operation was
58 * canceled and the element was not dropped. The original dragend event will
59 * be provided in the local event variable.
60 * - dnd-dragend Callback that is invoked when the drag operation ended. Available local
61 * variables are event and dropEffect.
62 * - dnd-selected Callback that is invoked when the element was clicked but not dragged.
63 * The original click event will be provided in the local event variable.
64 * - dnd-callback Custom callback that is passed to dropzone callbacks and can be used to
65 * communicate between source and target scopes. The dropzone can pass user
66 * defined variables to this callback.
69 * - dndDragging This class will be added to the element while the element is being
70 * dragged. It will affect both the element you see while dragging and the
71 * source element that stays at it's position. Do not try to hide the source
72 * element with this class, because that will abort the drag operation.
73 * - dndDraggingSource This class will be added to the element after the drag operation was
74 * started, meaning it only affects the original element that is still at
75 * it's source position, and not the "element" that the user is dragging with
78 dndLists.directive('dndDraggable', ['$parse', '$timeout', function($parse, $timeout) {
79 return function(scope, element, attr) {
80 // Set the HTML5 draggable attribute on the element.
81 element.attr("draggable", "true");
83 // If the dnd-disable-if attribute is set, we have to watch that.
84 if (attr.dndDisableIf) {
85 scope.$watch(attr.dndDisableIf, function(disabled) {
86 element.attr("draggable", !disabled);
91 * When the drag operation is started we have to prepare the dataTransfer object,
92 * which is the primary way we communicate with the target element
94 element.on('dragstart', function(event) {
95 event = event.originalEvent || event;
97 // Check whether the element is draggable, since dragstart might be triggered on a child.
98 if (element.attr('draggable') == 'false') return true;
100 // Initialize global state.
101 dndState.isDragging = true;
102 dndState.itemType = attr.dndType && scope.$eval(attr.dndType).toLowerCase();
104 // Set the allowed drop effects. See below for special IE handling.
105 dndState.dropEffect = "none";
106 dndState.effectAllowed = attr.dndEffectAllowed || ALL_EFFECTS[0];
107 event.dataTransfer.effectAllowed = dndState.effectAllowed;
109 // Internet Explorer and Microsoft Edge don't support custom mime types, see design doc:
110 // https://github.com/marceljuenemann/angular-drag-and-drop-lists/wiki/Data-Transfer-Design
111 var item = scope.$eval(attr.dndDraggable);
112 var mimeType = MIME_TYPE + (dndState.itemType ? ('-' + dndState.itemType) : '');
114 event.dataTransfer.setData(mimeType, angular.toJson(item));
116 // Setting a custom MIME type did not work, we are probably in IE or Edge.
117 var data = angular.toJson({item: item, type: dndState.itemType});
119 event.dataTransfer.setData(EDGE_MIME_TYPE, data);
121 // We are in Internet Explorer and can only use the Text MIME type. Also note that IE
122 // does not allow changing the cursor in the dragover event, therefore we have to choose
123 // the one we want to display now by setting effectAllowed.
124 var effectsAllowed = filterEffects(ALL_EFFECTS, dndState.effectAllowed);
125 event.dataTransfer.effectAllowed = effectsAllowed[0];
126 event.dataTransfer.setData(MSIE_MIME_TYPE, data);
130 // Add CSS classes. See documentation above.
131 element.addClass("dndDragging");
132 $timeout(function() { element.addClass("dndDraggingSource"); }, 0);
134 // Try setting a proper drag image if triggered on a dnd-handle (won't work in IE).
135 if (event._dndHandle && event.dataTransfer.setDragImage) {
136 event.dataTransfer.setDragImage(element[0], 0, 0);
139 // Invoke dragstart callback and prepare extra callback for dropzone.
140 $parse(attr.dndDragstart)(scope, {event: event});
141 if (attr.dndCallback) {
142 var callback = $parse(attr.dndCallback);
143 dndState.callback = function(params) { return callback(scope, params || {}); };
146 event.stopPropagation();
150 * The dragend event is triggered when the element was dropped or when the drag
151 * operation was aborted (e.g. hit escape button). Depending on the executed action
152 * we will invoke the callbacks specified with the dnd-moved or dnd-copied attribute.
154 element.on('dragend', function(event) {
155 event = event.originalEvent || event;
157 // Invoke callbacks. Usually we would use event.dataTransfer.dropEffect to determine
158 // the used effect, but Chrome has not implemented that field correctly. On Windows
159 // it always sets it to 'none', while Chrome on Linux sometimes sets it to something
160 // else when it's supposed to send 'none' (drag operation aborted).
161 scope.$apply(function() {
162 var dropEffect = dndState.dropEffect;
163 var cb = {copy: 'dndCopied', link: 'dndLinked', move: 'dndMoved', none: 'dndCanceled'};
164 $parse(attr[cb[dropEffect]])(scope, {event: event});
165 $parse(attr.dndDragend)(scope, {event: event, dropEffect: dropEffect});
169 dndState.isDragging = false;
170 dndState.callback = undefined;
171 element.removeClass("dndDragging");
172 element.removeClass("dndDraggingSource");
173 event.stopPropagation();
175 // In IE9 it is possible that the timeout from dragstart triggers after the dragend handler.
176 $timeout(function() { element.removeClass("dndDraggingSource"); }, 0);
180 * When the element is clicked we invoke the callback function
181 * specified with the dnd-selected attribute.
183 element.on('click', function(event) {
184 if (!attr.dndSelected) return;
186 event = event.originalEvent || event;
187 scope.$apply(function() {
188 $parse(attr.dndSelected)(scope, {event: event});
191 // Prevent triggering dndSelected in parent elements.
192 event.stopPropagation();
196 * Workaround to make element draggable in IE9
198 element.on('selectstart', function() {
199 if (this.dragDrop) this.dragDrop();
205 * Use the dnd-list attribute to make your list element a dropzone. Usually you will add a single
206 * li element as child with the ng-repeat directive. If you don't do that, we will not be able to
207 * position the dropped element correctly. If you want your list to be sortable, also add the
208 * dnd-draggable directive to your li element(s).
211 * - dnd-list Required attribute. The value has to be the array in which the data of
212 * the dropped element should be inserted. The value can be blank if used
213 * with a custom dnd-drop handler that always returns true.
214 * - dnd-allowed-types Optional array of allowed item types. When used, only items that had a
215 * matching dnd-type attribute will be dropable. Upper case characters will
216 * automatically be converted to lower case.
217 * - dnd-effect-allowed Optional string expression that limits the drop effects that can be
218 * performed in the list. See dnd-effect-allowed on dnd-draggable for more
219 * details on allowed options. The default value is all.
220 * - dnd-disable-drop-if Optional boolean expresssion. When it evaluates to true, no dropping
221 * into the list is possible. Note that this also disables rearranging
222 * items inside the list.
223 * - dnd-horizontal-list Optional boolean expresssion. When it evaluates to true, the positioning
224 * algorithm will use the left and right halfs of the list items instead of
225 * the upper and lower halfs.
226 * - dnd-external-sources Optional boolean expression. When it evaluates to true, the list accepts
227 * drops from sources outside of the current browser tab. This allows to
228 * drag and drop accross different browser tabs. The only major browser
229 * that does not support this is currently Microsoft Edge.
232 * - dnd-dragover Optional expression that is invoked when an element is dragged over the
233 * list. If the expression is set, but does not return true, the element is
234 * not allowed to be dropped. The following variables will be available:
235 * - event: The original dragover event sent by the browser.
236 * - index: The position in the list at which the element would be dropped.
237 * - type: The dnd-type set on the dnd-draggable, or undefined if non was
238 * set. Will be null for drops from external sources in IE and Edge,
239 * since we don't know the type in those cases.
240 * - dropEffect: One of move, copy or link, see dnd-effect-allowed.
241 * - external: Whether the element was dragged from an external source.
242 * - callback: If dnd-callback was set on the source element, this is a
243 * function reference to the callback. The callback can be invoked with
244 * custom variables like this: callback({var1: value1, var2: value2}).
245 * The callback will be executed on the scope of the source element. If
246 * dnd-external-sources was set and external is true, this callback will
248 * - dnd-drop Optional expression that is invoked when an element is dropped on the
249 * list. The same variables as for dnd-dragover will be available, with the
250 * exception that type is always known and therefore never null. There
251 * will also be an item variable, which is the transferred object. The
252 * return value determines the further handling of the drop:
253 * - falsy: The drop will be canceled and the element won't be inserted.
254 * - true: Signalises that the drop is allowed, but the dnd-drop
255 * callback already took care of inserting the element.
256 * - otherwise: All other return values will be treated as the object to
257 * insert into the array. In most cases you want to simply return the
258 * item parameter, but there are no restrictions on what you can return.
259 * - dnd-inserted Optional expression that is invoked after a drop if the element was
260 * actually inserted into the list. The same local variables as for
261 * dnd-drop will be available. Note that for reorderings inside the same
262 * list the old element will still be in the list due to the fact that
263 * dnd-moved was not called yet.
266 * - dndPlaceholder When an element is dragged over the list, a new placeholder child
267 * element will be added. This element is of type li and has the class
268 * dndPlaceholder set. Alternatively, you can define your own placeholder
269 * by creating a child element with dndPlaceholder class.
270 * - dndDragover Will be added to the list while an element is dragged over the list.
272 dndLists.directive('dndList', ['$parse', function($parse) {
273 return function(scope, element, attr) {
274 // While an element is dragged over the list, this placeholder element is inserted
275 // at the location where the element would be inserted after dropping.
276 var placeholder = getPlaceholderElement();
277 placeholder.remove();
279 var placeholderNode = placeholder[0];
280 var listNode = element[0];
281 var listSettings = {};
284 * The dragenter event is fired when a dragged element or text selection enters a valid drop
285 * target. According to the spec, we either need to have a dropzone attribute or listen on
286 * dragenter events and call preventDefault(). It should be noted though that no browser seems
287 * to enforce this behaviour.
289 element.on('dragenter', function (event) {
290 event = event.originalEvent || event;
292 // Calculate list properties, so that we don't have to repeat this on every dragover event.
293 var types = attr.dndAllowedTypes && scope.$eval(attr.dndAllowedTypes);
295 allowedTypes: angular.isArray(types) && types.join('|').toLowerCase().split('|'),
296 disabled: attr.dndDisableDropIf && scope.$eval(attr.dndDisableDropIf),
297 externalSources: attr.dndExternalSources && scope.$eval(attr.dndExternalSources),
298 horizontal: attr.dndHorizontalList && scope.$eval(attr.dndHorizontalList)
301 var mimeType = getMimeType(event.dataTransfer.types);
302 if (!mimeType || !isDropAllowed(getItemType(mimeType))) return true;
303 event.preventDefault();
307 * The dragover event is triggered "every few hundred milliseconds" while an element
308 * is being dragged over our list, or over an child element.
310 element.on('dragover', function(event) {
311 event = event.originalEvent || event;
313 // Check whether the drop is allowed and determine mime type.
314 var mimeType = getMimeType(event.dataTransfer.types);
315 var itemType = getItemType(mimeType);
316 if (!mimeType || !isDropAllowed(itemType)) return true;
318 // Make sure the placeholder is shown, which is especially important if the list is empty.
319 if (placeholderNode.parentNode != listNode) {
320 element.append(placeholder);
323 if (event.target != listNode) {
324 // Try to find the node direct directly below the list node.
325 var listItemNode = event.target;
326 while (listItemNode.parentNode != listNode && listItemNode.parentNode) {
327 listItemNode = listItemNode.parentNode;
330 if (listItemNode.parentNode == listNode && listItemNode != placeholderNode) {
331 // If the mouse pointer is in the upper half of the list item element,
332 // we position the placeholder before the list item, otherwise after it.
333 var rect = listItemNode.getBoundingClientRect();
334 if (listSettings.horizontal) {
335 var isFirstHalf = event.clientX < rect.left + rect.width / 2;
337 var isFirstHalf = event.clientY < rect.top + rect.height / 2;
339 listNode.insertBefore(placeholderNode,
340 isFirstHalf ? listItemNode : listItemNode.nextSibling);
344 // In IE we set a fake effectAllowed in dragstart to get the correct cursor, we therefore
345 // ignore the effectAllowed passed in dataTransfer. We must also not access dataTransfer for
346 // drops from external sources, as that throws an exception.
347 var ignoreDataTransfer = mimeType == MSIE_MIME_TYPE;
348 var dropEffect = getDropEffect(event, ignoreDataTransfer);
349 if (dropEffect == 'none') return stopDragover();
351 // At this point we invoke the callback, which still can disallow the drop.
352 // We can't do this earlier because we want to pass the index of the placeholder.
353 if (attr.dndDragover && !invokeCallback(attr.dndDragover, event, dropEffect, itemType)) {
354 return stopDragover();
357 // Set dropEffect to modify the cursor shown by the browser, unless we're in IE, where this
358 // is not supported. This must be done after preventDefault in Firefox.
359 event.preventDefault();
360 if (!ignoreDataTransfer) {
361 event.dataTransfer.dropEffect = dropEffect;
364 element.addClass("dndDragover");
365 event.stopPropagation();
370 * When the element is dropped, we use the position of the placeholder element as the
371 * position where we insert the transferred data. This assumes that the list has exactly
372 * one child element per array element.
374 element.on('drop', function(event) {
375 event = event.originalEvent || event;
377 // Check whether the drop is allowed and determine mime type.
378 var mimeType = getMimeType(event.dataTransfer.types);
379 var itemType = getItemType(mimeType);
380 if (!mimeType || !isDropAllowed(itemType)) return true;
382 // The default behavior in Firefox is to interpret the dropped element as URL and
383 // forward to it. We want to prevent that even if our drop is aborted.
384 event.preventDefault();
386 // Unserialize the data that was serialized in dragstart.
388 var data = JSON.parse(event.dataTransfer.getData(mimeType));
390 return stopDragover();
393 // Drops with invalid types from external sources might not have been filtered out yet.
394 if (mimeType == MSIE_MIME_TYPE || mimeType == EDGE_MIME_TYPE) {
395 itemType = data.type || undefined;
397 if (!isDropAllowed(itemType)) return stopDragover();
400 // Special handling for internal IE drops, see dragover handler.
401 var ignoreDataTransfer = mimeType == MSIE_MIME_TYPE;
402 var dropEffect = getDropEffect(event, ignoreDataTransfer);
403 if (dropEffect == 'none') return stopDragover();
405 // Invoke the callback, which can transform the transferredObject and even abort the drop.
406 var index = getPlaceholderIndex();
408 data = invokeCallback(attr.dndDrop, event, dropEffect, itemType, index, data);
409 if (!data) return stopDragover();
412 // The drop is definitely going to happen now, store the dropEffect.
413 dndState.dropEffect = dropEffect;
414 if (!ignoreDataTransfer) {
415 event.dataTransfer.dropEffect = dropEffect;
418 // Insert the object into the array, unless dnd-drop took care of that (returned true).
420 scope.$apply(function() {
421 scope.$eval(attr.dndList).splice(index, 0, data);
424 invokeCallback(attr.dndInserted, event, dropEffect, itemType, index, data);
428 event.stopPropagation();
433 * We have to remove the placeholder when the element is no longer dragged over our list. The
434 * problem is that the dragleave event is not only fired when the element leaves our list,
435 * but also when it leaves a child element. Therefore, we determine whether the mouse cursor
436 * is still pointing to an element inside the list or not.
438 element.on('dragleave', function(event) {
439 event = event.originalEvent || event;
441 var newTarget = document.elementFromPoint(event.clientX, event.clientY);
442 if (listNode.contains(newTarget) && !event._dndPhShown) {
443 // Signalize to potential parent lists that a placeholder is already shown.
444 event._dndPhShown = true;
451 * Given the types array from the DataTransfer object, returns the first valid mime type.
452 * A type is valid if it starts with MIME_TYPE, or it equals MSIE_MIME_TYPE or EDGE_MIME_TYPE.
454 function getMimeType(types) {
455 if (!types) return MSIE_MIME_TYPE; // IE 9 workaround.
456 for (var i = 0; i < types.length; i++) {
457 if (types[i] == MSIE_MIME_TYPE || types[i] == EDGE_MIME_TYPE ||
458 types[i].substr(0, MIME_TYPE.length) == MIME_TYPE) {
466 * Determines the type of the item from the dndState, or from the mime type for items from
467 * external sources. Returns undefined if no item type was set and null if the item type could
470 function getItemType(mimeType) {
471 if (dndState.isDragging) return dndState.itemType || undefined;
472 if (mimeType == MSIE_MIME_TYPE || mimeType == EDGE_MIME_TYPE) return null;
473 return (mimeType && mimeType.substr(MIME_TYPE.length + 1)) || undefined;
477 * Checks various conditions that must be fulfilled for a drop to be allowed, including the
478 * dnd-allowed-types attribute. If the item Type is unknown (null), the drop will be allowed.
480 function isDropAllowed(itemType) {
481 if (listSettings.disabled) return false;
482 if (!listSettings.externalSources && !dndState.isDragging) return false;
483 if (!listSettings.allowedTypes || itemType === null) return true;
484 return itemType && listSettings.allowedTypes.indexOf(itemType) != -1;
488 * Determines which drop effect to use for the given event. In Internet Explorer we have to
489 * ignore the effectAllowed field on dataTransfer, since we set a fake value in dragstart.
490 * In those cases we rely on dndState to filter effects. Read the design doc for more details:
491 * https://github.com/marceljuenemann/angular-drag-and-drop-lists/wiki/Data-Transfer-Design
493 function getDropEffect(event, ignoreDataTransfer) {
494 var effects = ALL_EFFECTS;
495 if (!ignoreDataTransfer) {
496 effects = filterEffects(effects, event.dataTransfer.effectAllowed);
498 if (dndState.isDragging) {
499 effects = filterEffects(effects, dndState.effectAllowed);
501 if (attr.dndEffectAllowed) {
502 effects = filterEffects(effects, attr.dndEffectAllowed);
504 // MacOS automatically filters dataTransfer.effectAllowed depending on the modifier keys,
505 // therefore the following modifier keys will only affect other operating systems.
506 if (!effects.length) {
508 } else if (event.ctrlKey && effects.indexOf('copy') != -1) {
510 } else if (event.altKey && effects.indexOf('link') != -1) {
518 * Small helper function that cleans up if we aborted a drop.
520 function stopDragover() {
521 placeholder.remove();
522 element.removeClass("dndDragover");
527 * Invokes a callback with some interesting parameters and returns the callbacks return value.
529 function invokeCallback(expression, event, dropEffect, itemType, index, item) {
530 return $parse(expression)(scope, {
531 callback: dndState.callback,
532 dropEffect: dropEffect,
534 external: !dndState.isDragging,
535 index: index !== undefined ? index : getPlaceholderIndex(),
536 item: item || undefined,
542 * We use the position of the placeholder node to determine at which position of the array the
543 * object needs to be inserted
545 function getPlaceholderIndex() {
546 return Array.prototype.indexOf.call(listNode.children, placeholderNode);
550 * Tries to find a child element that has the dndPlaceholder class set. If none was found, a
551 * new li element is created.
553 function getPlaceholderElement() {
555 angular.forEach(element.children(), function(childNode) {
556 var child = angular.element(childNode);
557 if (child.hasClass('dndPlaceholder')) {
561 return placeholder || angular.element("<li class='dndPlaceholder'></li>");
567 * Use the dnd-nodrag attribute inside of dnd-draggable elements to prevent them from starting
568 * drag operations. This is especially useful if you want to use input elements inside of
569 * dnd-draggable elements or create specific handle elements. Note: This directive does not work
570 * in Internet Explorer 9.
572 dndLists.directive('dndNodrag', function() {
573 return function(scope, element, attr) {
574 // Set as draggable so that we can cancel the events explicitly
575 element.attr("draggable", "true");
578 * Since the element is draggable, the browser's default operation is to drag it on dragstart.
579 * We will prevent that and also stop the event from bubbling up.
581 element.on('dragstart', function(event) {
582 event = event.originalEvent || event;
584 if (!event._dndHandle) {
585 // If a child element already reacted to dragstart and set a dataTransfer object, we will
586 // allow that. For example, this is the case for user selections inside of input elements.
587 if (!(event.dataTransfer.types && event.dataTransfer.types.length)) {
588 event.preventDefault();
590 event.stopPropagation();
595 * Stop propagation of dragend events, otherwise dnd-moved might be triggered and the element
598 element.on('dragend', function(event) {
599 event = event.originalEvent || event;
600 if (!event._dndHandle) {
601 event.stopPropagation();
608 * Use the dnd-handle directive within a dnd-nodrag element in order to allow dragging with that
609 * element after all. Therefore, by combining dnd-nodrag and dnd-handle you can allow
610 * dnd-draggable elements to only be dragged via specific "handle" elements. Note that Internet
611 * Explorer will show the handle element as drag image instead of the dnd-draggable element. You
612 * can work around this by styling the handle element differently when it is being dragged. Use
613 * the CSS selector .dndDragging:not(.dndDraggingSource) [dnd-handle] for that.
615 dndLists.directive('dndHandle', function() {
616 return function(scope, element, attr) {
617 element.attr("draggable", "true");
619 element.on('dragstart dragend', function(event) {
620 event = event.originalEvent || event;
621 event._dndHandle = true;
627 * Filters an array of drop effects using a HTML5 effectAllowed string.
629 function filterEffects(effects, effectAllowed) {
630 if (effectAllowed == 'all') return effects;
631 return effects.filter(function(effect) {
632 return effectAllowed.toLowerCase().indexOf(effect) != -1;
637 * For some features we need to maintain global state. This is done here, with these fields:
638 * - callback: A callback function set at dragstart that is passed to internal dropzone handlers.
639 * - dropEffect: Set in dragstart to "none" and to the actual value in the drop handler. We don't
640 * rely on the dropEffect passed by the browser, since there are various bugs in Chrome and
641 * Safari, and Internet Explorer defaults to copy if effectAllowed is copyMove.
642 * - effectAllowed: Set in dragstart based on dnd-effect-allowed. This is needed for IE because
643 * setting effectAllowed on dataTransfer might result in an undesired cursor.
644 * - isDragging: True between dragstart and dragend. Falsy for drops from external sources.
645 * - itemType: The item type of the dragged element set via dnd-type. This is needed because IE
646 * and Edge don't support custom mime types that we can use to transfer this information.
650 })(angular.module('dndLists', []));