diff --git a/angular-draganddrop.js b/angular-draganddrop.js index 8ec6b41..f38761f 100644 --- a/angular-draganddrop.js +++ b/angular-draganddrop.js @@ -66,6 +66,9 @@ function draggableDirective($parse) { var dragStartHandler = $parse(attrs.dragStart); var dragEndHandler = $parse(attrs.dragEnd); + var rawDragStartHandler = $parse(attrs.dragStartRaw); + var rawDragEndHandler = $parse(attrs.dragEndRaw); + domElement.addEventListener('dragstart', dragStartListener); domElement.addEventListener('dragend', dragEndListener); @@ -103,10 +106,15 @@ function draggableDirective($parse) { // Set drag data and drag type. event.dataTransfer.setData('json/' + draggableType, jsonData); - // Call custom handler - scope.$apply(function () { - dragStartHandler(scope, { $data: data, $event: event }); - }); + // Call custom handlers + if (attrs.dragStartRaw) { + rawDragStartHandler(scope, { $data: data, $event: event }); + } + if (attrs.dragStart) { + scope.$apply(function () { + dragStartHandler(scope, { $data: data, $event: event }); + }); + } event.stopPropagation(); } @@ -119,10 +127,15 @@ function draggableDirective($parse) { // Eval and serialize data. var data = safeDraggableDataEval(); - // Call custom handler - scope.$apply(function () { - dragEndHandler(scope, { $data: data, $event: event }); - }); + // Call custom handlers + if (attrs.dragEndRaw) { + rawDragEndHandler(scope, { $data: data, $event: event }); + } + if (attrs.dragEnd) { + scope.$apply(function () { + dragEndHandler(scope, { $data: data, $event: event }); + }); + } event.stopPropagation(); } @@ -165,6 +178,11 @@ function dropDirective($parse) { var dragLeaveHandler = $parse(attrs.dragLeave); var dropHandler = $parse(attrs.drop); + var rawDragOverHandler = $parse(attrs.dragOverRaw); + var rawDragEnterHandler = $parse(attrs.dragEnterRaw); + var rawDragLeaveHandler = $parse(attrs.dragLeaveRaw); + var rawDropHandler = $parse(attrs.dropRaw); + domElement.addEventListener('dragover', dragOverListener); domElement.addEventListener('dragenter', dragEnterListener); domElement.addEventListener('dragleave', dragLeaveListener); @@ -204,14 +222,18 @@ function dropDirective($parse) { } throttledDragover = now; - if (! attrs.dragOver) return; - - // Call custom handler + // Call custom handlers var data = getData(event); - scope.$apply(function () { - debugDroppable(scope, 'dragover callback !'); - dragOverHandler(scope, { $data: data, $event: event }); - }); + if (attrs.dragOverRaw) { + debugDroppable(scope, 'raw dragover callback !'); + rawDragOverHandler(scope, { $data: data, $event: event }); + } + if (attrs.dragOver) { + scope.$apply(function () { + debugDroppable(scope, 'dragover callback !'); + dragOverHandler(scope, { $data: data, $event: event }); + }); + } } function dragEnterListener(event) { @@ -225,13 +247,18 @@ function dropDirective($parse) { if (dragOverClass) element.addClass(dragOverClass); - if (! attrs.dragEnter) return; - - // Call custom handler + // Call custom handlers var data = getData(event); - scope.$apply(function () { - dragEnterHandler(scope, { $data: data, $event: event }); - }); + if (attrs.dragEnterRaw) { + debugDroppable(scope, 'raw dragenter callback !'); + rawDragEnterHandler(scope, { $data: data, $event: event }); + } + if (attrs.dragEnter) { + scope.$apply(function () { + debugDroppable(scope, 'dragenter callback !'); + dragEnterHandler(scope, { $data: data, $event: event }); + }); + } } function dragLeaveListener(event) { @@ -245,13 +272,18 @@ function dropDirective($parse) { element.removeClass(dragOverClass); - if (! attrs.dragLeave) return; - - // Call custom handler + // Call custom handlers var data = getData(event); - scope.$apply(function () { - dragLeaveHandler(scope, { $data: data, $event: event }); - }); + if (attrs.dragLeaveRaw) { + debugDroppable(scope, 'raw dragleave callback !'); + rawDragLeaveHandler(scope, { $data: data, $event: event }); + } + if (attrs.dragLeave) { + scope.$apply(function () { + debugDroppable(scope, 'dragleave callback !'); + dragLeaveHandler(scope, { $data: data, $event: event }); + }); + } } function dropListener(event) { @@ -262,11 +294,18 @@ function dropDirective($parse) { element.removeClass(dragOverClass); - // Call custom handler + // Call custom handlers var data = getData(event); - scope.$apply(function () { - dropHandler(scope, { $data: data, $event: event }); - }); + if (attrs.dropRaw) { + debugDroppable(scope, 'raw drop callback !'); + rawDropHandler(scope, { $data: data, $event: event }); + } + if (attrs.drop) { + scope.$apply(function () { + debugDroppable(scope, 'drop callback !'); + dropHandler(scope, { $data: data, $event: event }); + }); + } } /** diff --git a/test/draggable.js b/test/draggable.js index bd406fd..9f6d870 100644 --- a/test/draggable.js +++ b/test/draggable.js @@ -160,36 +160,98 @@ describe('Draggable directive', function () { }); describe('handlers', function() { + var digestWasInProgress; + beforeEach(function() { + digestWasInProgress = undefined; + $scope.onEvent = sinon.spy(function () { + digestWasInProgress = !!$scope.$$phase; + }); + }); - describe('dragstart', function() { + describe('"drag-start-raw"', function() { beforeEach(function() { - $scope.onDrag = sinon.spy(); var tpl = '
'; + 'drag-start-raw="onEvent($event, $data)">'; createElement(tpl); dispatchEvent(element, testDragStartEvent); }); it('should be called on dragstart when present', function() { - expect($scope.onDrag).to.have.been.calledOnce; + expect($scope.onEvent).to.have.been.calledOnce; + }); + + it('should provide correct $event and $data', function() { + expect($scope.onEvent.firstCall.args[0], '$event').to.equal(testDragStartEvent); + expect($scope.onEvent.firstCall.args[1], '$data').to.deep.equal({ + foo: 'bar' + }); + }); + + it('should NOT be called in an Angular $apply context', function() { + expect(digestWasInProgress).to.be.false; + }); + }); + + describe('"drag-start', function() { + beforeEach(function() { + var tpl = ''; + createElement(tpl); + + dispatchEvent(element, testDragStartEvent); + }); + + it('should be called on dragstart when present', function() { + expect($scope.onEvent).to.have.been.calledOnce; + }); + + it('should provide correct $event and $data', function() { + expect($scope.onEvent.firstCall.args[0], '$event').to.equal(testDragStartEvent); + expect($scope.onEvent.firstCall.args[1], '$data').to.deep.equal({ + foo: 'bar' + }); + }); + + it('should be called in an Angular $apply context', function() { + expect(digestWasInProgress).to.be.true; + }); + }); + + describe('"drag-end-raw"', function() { + beforeEach(function() { + var tpl = ''; + createElement(tpl); + + dispatchEvent(element, testDragStartEvent); + dispatchEvent(element, testDragEndEvent); + }); + + it('should be called on dragend when present', function() { + expect($scope.onEvent).to.have.been.calledOnce; }); it('should provide correct $event and $data', function() { - expect($scope.onDrag.firstCall.args[0], '$event').to.equal(testDragStartEvent); - expect($scope.onDrag.firstCall.args[1], '$data').to.deep.equal({ + expect($scope.onEvent.firstCall.args[0], '$event').to.equal(testDragEndEvent); + expect($scope.onEvent.firstCall.args[1], '$data').to.deep.equal({ foo: 'bar' }); }); + + it('should NOT be called in an Angular $apply context', function() { + expect(digestWasInProgress).to.be.false; + }); }); - describe('dragend', function() { + describe('"drag-end"', function() { beforeEach(function() { - $scope.onDrag = sinon.spy(); var tpl = ''; + 'drag-end="onEvent($event, $data)">'; createElement(tpl); dispatchEvent(element, testDragStartEvent); @@ -197,15 +259,19 @@ describe('Draggable directive', function () { }); it('should be called on dragend when present', function() { - expect($scope.onDrag).to.have.been.calledOnce; + expect($scope.onEvent).to.have.been.calledOnce; }); it('should provide correct $event and $data', function() { - expect($scope.onDrag.firstCall.args[0], '$event').to.equal(testDragEndEvent); - expect($scope.onDrag.firstCall.args[1], '$data').to.deep.equal({ + expect($scope.onEvent.firstCall.args[0], '$event').to.equal(testDragEndEvent); + expect($scope.onEvent.firstCall.args[1], '$data').to.deep.equal({ foo: 'bar' }); }); + + it('should be called in an Angular $apply context', function() { + expect(digestWasInProgress).to.be.true; + }); }); }); diff --git a/test/drop.js b/test/drop.js index 975e357..e44311e 100644 --- a/test/drop.js +++ b/test/drop.js @@ -229,97 +229,205 @@ describe('Drop directive', function () { }); describe('handlers', function() { - var expectedData = { - 'json/image': {foo: 'bar'}, - 'json': {foo: 'bar'}, - 'text/uri-list': 'http://dragdrop.com', - 'text': 'http://dragdrop.com' - }; + var expectedData; + var digestWasInProgress; + beforeEach(function() { + expectedData = { + 'json/image': {foo: 'bar'}, + 'json': {foo: 'bar'}, + 'text/uri-list': 'http://dragdrop.com', + 'text': 'http://dragdrop.com' + }; + digestWasInProgress = undefined; + $scope.onEvent = sinon.spy(function () { + digestWasInProgress = !!$scope.$$phase; + }); + }); + + describe('"drag-enter-raw"', function() { + beforeEach(function() { + var tpl = ''; + createElement(tpl); + + dispatchEvent(element, dragEnterEvent); + }); + + it('should be called on dragenter when present', function() { + expect($scope.onEvent).to.have.been.calledOnce; + }); + + it('should provide correct $event and $data', function() { + expect($scope.onEvent.firstCall.args[0], '$event').to.equal(dragEnterEvent); + expect($scope.onEvent.firstCall.args[1], '$data').to.deep.equal(expectedData); + }); + + it('should NOT be called in an Angular $apply context', function() { + expect(digestWasInProgress).to.be.false; + }); + }); describe('"drag-enter"', function() { beforeEach(function() { - $scope.onDrag = sinon.spy(); - var tpl = ''; + var tpl = ''; createElement(tpl); dispatchEvent(element, dragEnterEvent); }); it('should be called on dragenter when present', function() { - expect($scope.onDrag).to.have.been.calledOnce; + expect($scope.onEvent).to.have.been.calledOnce; }); it('should provide correct $event and $data', function() { - expect($scope.onDrag.firstCall.args[0], '$event').to.equal(dragEnterEvent); - expect($scope.onDrag.firstCall.args[1], '$data').to.deep.equal(expectedData); + expect($scope.onEvent.firstCall.args[0], '$event').to.equal(dragEnterEvent); + expect($scope.onEvent.firstCall.args[1], '$data').to.deep.equal(expectedData); + }); + + it('should be called in an Angular $apply context', function() { + expect(digestWasInProgress).to.be.true; + }); + }); + + describe('"drag-leave-raw"', function() { + beforeEach(function() { + var tpl = ''; + createElement(tpl); + + dispatchEvent(element, dragLeaveEvent); + }); + + it('should be called on dragleave when present', function() { + expect($scope.onEvent).to.have.been.calledOnce; + }); + + it('should provide correct $event and $data', function() { + expect($scope.onEvent.firstCall.args[0], '$event').to.equal(dragLeaveEvent); + expect($scope.onEvent.firstCall.args[1], '$data').to.deep.equal(expectedData); + }); + + it('should NOT be called in an Angular $apply context', function() { + expect(digestWasInProgress).to.be.false; }); }); describe('"drag-leave"', function() { beforeEach(function() { - $scope.onDrag = sinon.spy(); - var tpl = ''; + var tpl = ''; createElement(tpl); dispatchEvent(element, dragLeaveEvent); }); it('should be called on dragleave when present', function() { - expect($scope.onDrag).to.have.been.calledOnce; + expect($scope.onEvent).to.have.been.calledOnce; }); it('should provide correct $event and $data', function() { - expect($scope.onDrag.firstCall.args[0], '$event').to.equal(dragLeaveEvent); - expect($scope.onDrag.firstCall.args[1], '$data').to.deep.equal(expectedData); + expect($scope.onEvent.firstCall.args[0], '$event').to.equal(dragLeaveEvent); + expect($scope.onEvent.firstCall.args[1], '$data').to.deep.equal(expectedData); + }); + + it('should be called in an Angular $apply context', function() { + expect(digestWasInProgress).to.be.true; + }); + }); + + describe('"drag-over-raw"', function() { + beforeEach(function() { + var tpl = ''; + createElement(tpl); + + dispatchEvent(element, dragOverEvent); + }); + + it('should be called on dragover when present', function() { + expect($scope.onEvent).to.have.been.calledOnce; + }); + + it('should provide correct $event and $data', function() { + expect($scope.onEvent.firstCall.args[0], '$event').to.equal(dragOverEvent); + expect($scope.onEvent.firstCall.args[1], '$data').to.deep.equal(expectedData); + }); + + it('should NOT be called in an Angular $apply context', function() { + expect(digestWasInProgress).to.be.false; }); }); describe('"drag-over"', function() { beforeEach(function() { - $scope.onDrag = sinon.spy(); - var tpl = ''; + var tpl = ''; createElement(tpl); dispatchEvent(element, dragOverEvent); }); it('should be called on dragover when present', function() { - expect($scope.onDrag).to.have.been.calledOnce; + expect($scope.onEvent).to.have.been.calledOnce; }); it('should provide correct $event and $data', function() { - expect($scope.onDrag.firstCall.args[0], '$event').to.equal(dragOverEvent); - expect($scope.onDrag.firstCall.args[1], '$data').to.deep.equal(expectedData); + expect($scope.onEvent.firstCall.args[0], '$event').to.equal(dragOverEvent); + expect($scope.onEvent.firstCall.args[1], '$data').to.deep.equal(expectedData); + }); + + it('should be called in an Angular $apply context', function() { + expect(digestWasInProgress).to.be.true; + }); + }); + + describe('"drop-raw"', function() { + beforeEach(function() { + var tpl = ''; + createElement(tpl); + + dispatchEvent(element, dropEvent); + }); + + it('should be called on drop when present', function() { + expect($scope.onEvent).to.have.been.calledOnce; + }); + + it('should provide correct $event and $data', function() { + expect($scope.onEvent.firstCall.args[0], '$event').to.equal(dropEvent); + expect($scope.onEvent.firstCall.args[1], '$data').to.deep.equal(expectedData); + }); + + it('should NOT be called in an Angular $apply context', function() { + expect(digestWasInProgress).to.be.false; }); }); describe('"drop"', function() { beforeEach(function() { - $scope.onDrag = sinon.spy(); - var tpl = ''; + var tpl = ''; createElement(tpl); dispatchEvent(element, dropEvent); }); it('should be called on drop when present', function() { - expect($scope.onDrag).to.have.been.calledOnce; + expect($scope.onEvent).to.have.been.calledOnce; }); it('should provide correct $event and $data', function() { - expect($scope.onDrag.firstCall.args[0], '$event').to.equal(dropEvent); - expect($scope.onDrag.firstCall.args[1], '$data').to.deep.equal(expectedData); + expect($scope.onEvent.firstCall.args[0], '$event').to.equal(dropEvent); + expect($scope.onEvent.firstCall.args[1], '$data').to.deep.equal(expectedData); + }); + + it('should be called in an Angular $apply context', function() { + expect(digestWasInProgress).to.be.true; }); }); }); describe('throttling', function() { - // https://github.com/lemonde/angular-draganddrop/issues/2 + // TODO https://github.com/lemonde/angular-draganddrop/issues/2 it.skip('should limit the calls to the drag-over callback'); }); describe('automatic handling of HTML5 API quirks', function() { - // https://github.com/lemonde/angular-draganddrop/issues/2 + // TODO https://github.com/lemonde/angular-draganddrop/issues/2 it.skip('should call preventDefault() on selected events'); });