diff --git a/angular-drag-and-drop-lists.js b/angular-drag-and-drop-lists.js index 8a07ce3..185ccd5 100644 --- a/angular-drag-and-drop-lists.js +++ b/angular-drag-and-drop-lists.js @@ -97,6 +97,8 @@ // Check whether the element is draggable, since dragstart might be triggered on a child. if (element.attr('draggable') == 'false') return true; + var parentIsList = element.parent().length && element.parent()[0].hasAttribute('dnd-list'); + // Initialize global state. dndState.isDragging = true; dndState.itemType = attr.dndType && scope.$eval(attr.dndType).toLowerCase(); @@ -129,15 +131,28 @@ // Add CSS classes. See documentation above. element.addClass("dndDragging"); - $timeout(function() { element.addClass("dndDraggingSource"); }, 0); + // We'll set this now and add the class 'dndDraggingSource' to it when we start moving the item. + // This needs to be in sync with the process of insert the placeholder into the list + + if(parentIsList){ + dndState.currentDragItem = element; + }else{ + $timeout(function() { element.addClass("dndDraggingSource"); }, 0); + } + // 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 dragstart callback and prepare extra callback for dropzone. + // Invoke dragstart callback $parse(attr.dndDragstart)(scope, {event: event}); + + // we need to schedule a digest cycle to make sure other bits like ngHide fire + scope.$evalAsync(); + + // Prepare extra callback for dropzone if (attr.dndCallback) { var callback = $parse(attr.dndCallback); dndState.callback = function(params) { return callback(scope, params || {}); }; @@ -168,8 +183,10 @@ // Clean up dndState.isDragging = false; dndState.callback = undefined; + dndState.currentDragItem = undefined; element.removeClass("dndDragging"); element.removeClass("dndDraggingSource"); + event.stopPropagation(); // In IE9 it is possible that the timeout from dragstart triggers after the dragend handler. @@ -318,6 +335,14 @@ // Make sure the placeholder is shown, which is especially important if the list is empty. if (placeholderNode.parentNode != listNode) { element.append(placeholder); + + // We set the class here instead of in the dragstart, because if this class + // hides the original item, we want to make sure we do that at the same time + // that we put the placeholder on the dom. This prevents some UI flashing + if(dndState.currentDragItem){ + dndState.currentDragItem.addClass("dndDraggingSource"); + } + } if (event.target != listNode) { @@ -644,6 +669,8 @@ * - isDragging: True between dragstart and dragend. Falsy for drops from external sources. * - itemType: The item type of the dragged element set via dnd-type. This is needed because IE * and Edge don't support custom mime types that we can use to transfer this information. + * - currentDragItem: A reference to the current drag item. We set this on dragStart and then + * reference it when moving. */ var dndState = {}; diff --git a/angular-drag-and-drop-lists.min.js b/angular-drag-and-drop-lists.min.js index 0d957a6..90b5f45 100644 --- a/angular-drag-and-drop-lists.min.js +++ b/angular-drag-and-drop-lists.min.js @@ -7,43 +7,44 @@ * * License: MIT */ -!function(e){function n(e,n){return"all"==n?e:e.filter(function(e){return-1!=n.toLowerCase().indexOf(e)})}var a="application/x-dnd",r="application/json",t="Text",d=["move","copy","link"] -e.directive("dndDraggable",["$parse","$timeout",function(e,i){return function(l,f,c){f.attr("draggable","true"),c.dndDisableIf&&l.$watch(c.dndDisableIf,function(e){f.attr("draggable",!e)}),f.on("dragstart",function(s){if(s=s.originalEvent||s,"false"==f.attr("draggable"))return!0 -o.isDragging=!0,o.itemType=c.dndType&&l.$eval(c.dndType).toLowerCase(),o.dropEffect="none",o.effectAllowed=c.dndEffectAllowed||d[0],s.dataTransfer.effectAllowed=o.effectAllowed -var g=l.$eval(c.dndDraggable),u=a+(o.itemType?"-"+o.itemType:"") -try{s.dataTransfer.setData(u,angular.toJson(g))}catch(p){var v=angular.toJson({item:g,type:o.itemType}) -try{s.dataTransfer.setData(r,v)}catch(p){var D=n(d,o.effectAllowed) -s.dataTransfer.effectAllowed=D[0],s.dataTransfer.setData(t,v)}}if(f.addClass("dndDragging"),i(function(){f.addClass("dndDraggingSource")},0),s._dndHandle&&s.dataTransfer.setDragImage&&s.dataTransfer.setDragImage(f[0],0,0),e(c.dndDragstart)(l,{event:s}),c.dndCallback){var y=e(c.dndCallback) -o.callback=function(e){return y(l,e||{})}}s.stopPropagation()}),f.on("dragend",function(n){n=n.originalEvent||n,l.$apply(function(){var a=o.dropEffect,r={copy:"dndCopied",link:"dndLinked",move:"dndMoved",none:"dndCanceled"} -e(c[r[a]])(l,{event:n}),e(c.dndDragend)(l,{event:n,dropEffect:a})}),o.isDragging=!1,o.callback=void 0,f.removeClass("dndDragging"),f.removeClass("dndDraggingSource"),n.stopPropagation(),i(function(){f.removeClass("dndDraggingSource")},0)}),f.on("click",function(n){c.dndSelected&&(n=n.originalEvent||n,l.$apply(function(){e(c.dndSelected)(l,{event:n})}),n.stopPropagation())}),f.on("selectstart",function(){this.dragDrop&&this.dragDrop()})}}]),e.directive("dndList",["$parse",function(e){return function(i,l,f){function c(e){if(!e)return t -for(var n=0;n")}var T=y() -T.remove() -var h=T[0],m=l[0],E={} +!function(e){function n(e,n){return"all"==n?e:e.filter(function(e){return n.toLowerCase().indexOf(e)!=-1})}var r="application/x-dnd",a="application/json",t="Text",d=["move","copy","link"] +e.directive("dndDraggable",["$parse","$timeout",function(e,i){return function(l,f,c){f.attr("draggable","true"),c.dndDisableIf&&l.$watch(c.dndDisableIf,function(e){f.attr("draggable",!e)}),f.on("dragstart",function(g){if(g=g.originalEvent||g,"false"==f.attr("draggable"))return!0 +var s=f.parent().length&&f.parent()[0].hasAttribute("dnd-list") +o.isDragging=!0,o.itemType=c.dndType&&l.$eval(c.dndType).toLowerCase(),o.dropEffect="none",o.effectAllowed=c.dndEffectAllowed||d[0],g.dataTransfer.effectAllowed=o.effectAllowed +var u=l.$eval(c.dndDraggable),p=r+(o.itemType?"-"+o.itemType:"") +try{g.dataTransfer.setData(p,angular.toJson(u))}catch(e){var v=angular.toJson({item:u,type:o.itemType}) +try{g.dataTransfer.setData(a,v)}catch(e){var D=n(d,o.effectAllowed) +g.dataTransfer.effectAllowed=D[0],g.dataTransfer.setData(t,v)}}if(f.addClass("dndDragging"),s?o.currentDragItem=f:i(function(){f.addClass("dndDraggingSource")},0),g._dndHandle&&g.dataTransfer.setDragImage&&g.dataTransfer.setDragImage(f[0],0,0),e(c.dndDragstart)(l,{event:g}),l.$evalAsync(),c.dndCallback){var y=e(c.dndCallback) +o.callback=function(e){return y(l,e||{})}}g.stopPropagation()}),f.on("dragend",function(n){n=n.originalEvent||n,l.$apply(function(){var r=o.dropEffect,a={copy:"dndCopied",link:"dndLinked",move:"dndMoved",none:"dndCanceled"} +e(c[a[r]])(l,{event:n}),e(c.dndDragend)(l,{event:n,dropEffect:r})}),o.isDragging=!1,o.callback=void 0,o.currentDragItem=void 0,f.removeClass("dndDragging"),f.removeClass("dndDraggingSource"),n.stopPropagation(),i(function(){f.removeClass("dndDraggingSource")},0)}),f.on("click",function(n){c.dndSelected&&(n=n.originalEvent||n,l.$apply(function(){e(c.dndSelected)(l,{event:n})}),n.stopPropagation())}),f.on("selectstart",function(){this.dragDrop&&this.dragDrop()})}}]),e.directive("dndList",["$parse",function(e){return function(i,l,f){function c(e){if(!e)return t +for(var n=0;n")}var m=y() +m.remove() +var T=m[0],h=l[0],b={} l.on("dragenter",function(e){e=e.originalEvent||e var n=f.dndAllowedTypes&&i.$eval(f.dndAllowedTypes) -E={allowedTypes:angular.isArray(n)&&n.join("|").toLowerCase().split("|"),disabled:f.dndDisableIf&&i.$eval(f.dndDisableIf),externalSources:f.dndExternalSources&&i.$eval(f.dndExternalSources),horizontal:f.dndHorizontalList&&i.$eval(f.dndHorizontalList)} -var a=c(e.dataTransfer.types) -return a&&g(s(a))?void e.preventDefault():!0}),l.on("dragover",function(e){e=e.originalEvent||e -var n=c(e.dataTransfer.types),a=s(n) -if(!n||!g(a))return!0 -if(h.parentNode!=m&&l.append(T),e.target!=m){for(var r=e.target;r.parentNode!=m&&r.parentNode;)r=r.parentNode -if(r.parentNode==m&&r!=h){var d=r.getBoundingClientRect() -if(E.horizontal)var o=e.clientX'; + var DRAGGABLE_INSIDE_LIST_HTML = '
'; describe('constructor', function() { it('sets the draggable attribute', function() { @@ -75,9 +76,27 @@ describe('dndDraggable', function() { expect(Dragstart.on(element, {allowedMimeTypes: ['Text']}).effectAllowed).toBe('copy'); }); - it('adds CSS classes to element', inject(function($timeout) { + it('adds dndDragging class to element', inject(function($timeout) { Dragstart.on(element); expect(element.hasClass('dndDragging')).toBe(true); + })); + + it('adds dndDraggingSource class to element only after subsequent dragover if item is in a list', inject(function($timeout) { + var listWithDragItem = compileAndLink(DRAGGABLE_INSIDE_LIST_HTML); + element = listWithDragItem.find('[dnd-draggable]'); + + var dragstart = Dragstart.on(element); + expect(element.hasClass('dndDraggingSource')).toBe(false); + + $timeout.flush(0); + expect(element.hasClass('dndDraggingSource')).toBe(false); + + dragstart.dragover(listWithDragItem); + expect(element.hasClass('dndDraggingSource')).toBe(true); + })); + + it('adds CSS classes to element immediately if item is not in a list', inject(function($timeout) { + Dragstart.on(element); expect(element.hasClass('dndDraggingSource')).toBe(false); $timeout.flush(0); @@ -109,6 +128,7 @@ describe('dndDraggable', function() { beforeEach(function() { element = compileAndLink(SIMPLE_HTML); + target = compileAndLink('
'); dragstart = Dragstart.on(element); }); @@ -118,6 +138,7 @@ describe('dndDraggable', function() { it('removes CSS classes from element', inject(function($timeout) { $timeout.flush(0); + dragstart.dragover(target); expect(element.hasClass('dndDragging')).toBe(true); expect(element.hasClass('dndDraggingSource')).toBe(true);