diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..65365be --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ +# EditorConfig helps developers define and maintain consistent +# coding styles between different editors and IDEs +# editorconfig.org + +root = true + +[*] + +indent_style = space +indent_size = 2 + +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true diff --git a/.jshintrc b/.jshintrc new file mode 100644 index 0000000..bc1a1cc --- /dev/null +++ b/.jshintrc @@ -0,0 +1,53 @@ +{ + "bitwise": true, + "camelcase": true, + "curly": false, + "eqeqeq": true, + "es3": false, + "forin": true, + "immed": true, + "indent": 2, + "latedef": "nofunc", + "newcap": true, + "noarg": true, + "noempty": true, + "nonew": false, + "plusplus": false, + "quotmark": true, + "regexp": false, + "undef": true, + "unused": true, + "strict": true, + "trailing": true, + "maxparams": 4, + "maxdepth": 2, + "maxstatements": 30, + "maxcomplexity": 10, + "maxlen": 110, + + "asi": false, + "boss": false, + "debug": false, + "eqnull": true, + "esnext": false, + "evil": false, + "expr": false, + "funcscope": false, + "globalstrict": false, + "iterator": false, + "lastsemic": false, + "laxbreak": false, + "laxcomma": false, + "loopfunc": false, + "moz": false, + "multistr": true, + "proto": false, + "scripturl": false, + "smarttabs": false, + "shadow": false, + "sub": false, + "supernew": false, + "validthis": true, + + "node": true +} diff --git a/README.md b/README.md index e78cddc..b4485de 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,22 @@ # angular-draganddrop [![Build Status](https://travis-ci.org/lemonde/angular-draganddrop.svg?branch=master)](https://travis-ci.org/lemonde/angular-draganddrop) -[![Dependency Status](https://david-dm.org/neoziro/angular-draganddrop.svg?theme=shields.io)](https://david-dm.org/neoziro/angular-draganddrop) -[![devDependency Status](https://david-dm.org/neoziro/angular-draganddrop/dev-status.svg?theme=shields.io)](https://david-dm.org/neoziro/angular-draganddrop#info=devDependencies) +[![Dependency Status](https://david-dm.org/lemonde/angular-draganddrop.svg?theme=shields.io)](https://david-dm.org/lemonde/angular-draganddrop) +[![devDependency Status](https://david-dm.org/lemonde/angular-draganddrop/dev-status.svg?theme=shields.io)](https://david-dm.org/lemonde/angular-draganddrop#info=devDependencies) Drag and drop directives for Angular using native HTML5 API. + +## introduction +This module defines 2 directives : +* "draggable" for a draggable element +* "drop" for a drop zone +It handles data type control. + +Note HTML5 drag&drop is not trivial. Recommended reads : +* http://www.quirksmode.org/blog/archives/2009/09/the_html5_drag.html +* https://html.spec.whatwg.org/multipage/interaction.html#dnd + + ## Install ### Using bower @@ -65,22 +77,35 @@ angular.module('controllers.dragDrop', ['draganddrop']) ### "draggable" directive +Parameters : - "draggable" Make the element draggable. Accepts a boolean. - "effect-allowed" Allowed effects for the dragged element, see https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer#effectAllowed.28.29. Accepts a string. - "draggable-type" Type of data object attached to the dragged element, this type is prefixed by "json/". Accepts a string. - "draggable-data" Data attached to the dragged element, data are serialized in JSON. Accepts an Angular expression. +- "drag-start" (optional) an Angular expression to be evaluated on drag start ("dragstart" event). +- "drag-end" (optional) an Angular expression to be evaluated on drag end ("dragend" event). The draggable directive serializes data as JSON and prefix the specified type with "json/". ### "drop" directive +The drop directive automatically : +- calls the event.preventDefault() for dragenter and dragleave, as asked in the spec +- unserializes data with the "json" format, other data are not formatted +- throttles the dragover event (200ms) to avoid burning your CPU during the drag&drop + +Parameters : - "drop" Drop handler, executed on drop. Accepts an Angular expression. - "drop-effect" Drop effect to set, see https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer#dropEffect.28.29. Accepts a string. - "drop-accept" Types accepted or function to prevent unauthorized drag and drop. Accepts a string, an array, a function or a boolean. -- "drag-over" Drag over handler, executed on drag over. Accepts an Angular expression. - "drag-over-class" Class set on drag over, when the drag is authorized. Accepts a string. +- "drag-over" (optional) an Angular expression to be evaluated on drag over ("dragover" event). +- "drag-enter" (optional) an Angular expression to be evaluated on drag enter ("dragenter" event). +- "drag-leave" (optional) an Angular expression to be evaluated on drag leave ("dragleave" event). +- "drop" (optional) an Angular expression to be evaluated on drag over ("drop" event). + +Handlers are : `(scope, { $data: data, $event: event })` -The drop directive automatically unserializes data with the "json" format, other data are not formatted. ## Browsers support diff --git a/angular-draganddrop.js b/angular-draganddrop.js index 22156b3..2039d43 100644 --- a/angular-draganddrop.js +++ b/angular-draganddrop.js @@ -1,5 +1,3 @@ -/*! Angular draganddrop v0.2.1 | (c) 2013 Greg Bergé | License MIT */ - angular .module('draganddrop', []) .directive('draggable', ['$parse', draggableDirective]) @@ -46,7 +44,7 @@ function draggableDirective($parse) { var dragEndHandler = $parse(attrs.dragEnd); attrs.$observe('draggable', function (draggable) { - domElement.draggable = draggable !== 'false'; + domElement.draggable = (draggable !== 'false'); }); domElement.addEventListener('dragstart', dragStartListener); @@ -57,15 +55,28 @@ function draggableDirective($parse) { domElement.removeEventListener('dragend', dragEndListener); }); + // Convenience function to help the directive user. + // AngularJS default error is unclear. + function informativeDraggableDataEval() { + try { + return scope.$eval(draggableData); + } + catch(e) { + // throw a clearer error + throw new Error('draggable-data can\'t be parsed by Angular : ' + + 'check your draggable directive invocation !'); + } + } + function dragStartListener(event) { // Restrict drag effect. event.dataTransfer.effectAllowed = effectAllowed || event.dataTransfer.effectAllowed; // Eval and serialize data. - var data = scope.$eval(draggableData); + var data = informativeDraggableDataEval(); var jsonData = angular.toJson(data); - // Call dragStartHandler + // Call custom handler scope.$apply(function () { dragStartHandler(scope, { $data: data, $event: event }); }); @@ -78,9 +89,9 @@ function draggableDirective($parse) { function dragEndListener(event) { // Eval and serialize data. - var data = scope.$eval(draggableData); + var data = informativeDraggableDataEval(); - // Call dragEndHandler + // Call custom handler scope.$apply(function () { dragEndHandler(scope, { $data: data, $event: event }); }); @@ -118,15 +129,18 @@ function dropDirective($parse) { var dragOverClass = attrs.dragOverClass; var dragOverHandler = $parse(attrs.dragOver); + var dragEnterHandler = $parse(attrs.dragEnter); var dragLeaveHandler = $parse(attrs.dragLeave); var dropHandler = $parse(attrs.drop); domElement.addEventListener('dragover', dragOverListener); + domElement.addEventListener('dragenter', dragEnterListener); domElement.addEventListener('dragleave', dragLeaveListener); domElement.addEventListener('drop', dropListener); scope.$on('$destroy', function () { domElement.removeEventListener('dragover', dragOverListener); + domElement.removeEventListener('dragenter', dragEnterListener); domElement.removeEventListener('dragleave', dragLeaveListener); domElement.removeEventListener('drop', dropListener); }); @@ -137,45 +151,63 @@ function dropDirective($parse) { // Check if type is accepted. if (! accepts(scope.$eval(dropAccept), event)) return true; - if (dragOverClass) element.addClass(dragOverClass); - // Set up drop effect to link. event.dataTransfer.dropEffect = dropEffect || event.dataTransfer.dropEffect; // Prevent default to accept drag and drop. event.preventDefault(); + if (dragOverClass) element.addClass(dragOverClass); + var now = new Date().getTime(); if (now - throttledDragover < 200) return; throttledDragover = now; - if (!attrs.dragOver) return; + if (! attrs.dragOver) return; var data = getData(event); - // Call dragOverHandler + // Call custom handler scope.$apply(function () { dragOverHandler(scope, { $data: data, $event: event }); }); } - function dragLeaveListener(event) { + function dragEnterListener(event) { // Check if type is accepted. if (! accepts(scope.$eval(dropAccept), event)) return true; - removeDragOverClass(); + // Prevent default to accept drag and drop. + event.preventDefault(); - if (!attrs.dragLeave) return; + if (! attrs.dragEnter) return; var data = getData(event); - // Call dragLeaveHandler + // Call custom handler scope.$apply(function () { - dragLeaveHandler(scope, { $data: data, $event: event }); + dragEnterHandler(scope, { $data: data, $event: event }); }); + } + + + function dragLeaveListener(event) { + // Check if type is accepted. + if (! accepts(scope.$eval(dropAccept), event)) return true; // Prevent default to accept drag and drop. event.preventDefault(); + + removeDragOverClass(); + + if (! attrs.dragLeave) return; + + var data = getData(event); + + // Call custom handler + scope.$apply(function () { + dragLeaveHandler(scope, { $data: data, $event: event }); + }); } function dropListener(event) { @@ -183,7 +215,7 @@ function dropDirective($parse) { removeDragOverClass(); - // Call dropHandler + // Call custom handler scope.$apply(function () { dropHandler(scope, { $data: data, $event: event }); }); diff --git a/angular-draganddrop.min.js b/angular-draganddrop.min.js index 3e52119..c6f17bc 100644 --- a/angular-draganddrop.min.js +++ b/angular-draganddrop.min.js @@ -1,3 +1,2 @@ -/*! Angular draganddrop v0.2.1 | (c) 2013 Greg Bergé | License MIT */ -function draggableDirective(a){return{restrict:"A",link:function(b,c,d){function e(a){a.dataTransfer.effectAllowed=g||a.dataTransfer.effectAllowed;var c=b.$eval(h),d=angular.toJson(c);b.$apply(function(){k(b,{$data:c,$event:a})}),a.dataTransfer.setData("json/"+i,d),a.stopPropagation()}function f(a){var c=b.$eval(h);b.$apply(function(){l(b,{$data:c,$event:a})}),a.stopPropagation()}var g,h,i,j=c[0];d.$observe("effectAllowed",function(a){g=a}),d.$observe("draggableData",function(a){h=a}),d.$observe("draggableType",function(a){i=a});var k=a(d.dragStart),l=a(d.dragEnd);d.$observe("draggable",function(a){j.draggable="false"!==a}),j.addEventListener("dragstart",e),j.addEventListener("dragend",f),b.$on("$destroy",function(){j.removeEventListener("dragstart",e),j.removeEventListener("dragend",f)})}}}function dropDirective(a){return{restrict:"A",link:function(b,c,d){function e(a){if(!i(b.$eval(n),a))return!0;o&&c.addClass(o),a.dataTransfer.dropEffect=m||a.dataTransfer.dropEffect,a.preventDefault();var e=(new Date).getTime();if(!(200>e-s)&&(s=e,d.dragOver)){var f=j(a);b.$apply(function(){p(b,{$data:f,$event:a})})}}function f(a){if(!i(b.$eval(n),a))return!0;if(h(),d.dragLeave){var c=j(a);b.$apply(function(){q(b,{$data:c,$event:a})}),a.preventDefault()}}function g(a){var c=j(a);h(),b.$apply(function(){r(b,{$data:c,$event:a})}),a.preventDefault()}function h(){c.removeClass(o)}function i(a,b){return"boolean"==typeof a?a:"string"==typeof a?i([a],b):Array.isArray(a)?i(function(b){return b.some(function(b){return b=b.split("/"),a.some(function(a){a=a.split("/");var c=!0;return b.forEach(function(b,d){"*"!==a[d]&&b!==a[d]&&(c=!1)}),c})})},b):"function"==typeof a?a(k(b.dataTransfer.types)):!1}function j(a){var b=k(a.dataTransfer.types);return b.reduce(function(b,c){var d=a.dataTransfer.getData(c),e=c.split("/")[0];return"json"===e&&d&&(d=angular.fromJson(d)),b[c]=d,c.split("/").reduce(function(a,c){return a.push(c),b[a.join("/")]=d,a},[]),b},{})}function k(a){return Array.prototype.slice.call(a)}var l=c[0],m=d.dropEffect,n=d.dropAccept,o=d.dragOverClass,p=a(d.dragOver),q=a(d.dragLeave),r=a(d.drop);l.addEventListener("dragover",e),l.addEventListener("dragleave",f),l.addEventListener("drop",g),b.$on("$destroy",function(){l.removeEventListener("dragover",e),l.removeEventListener("dragleave",f),l.removeEventListener("drop",g)});var s=0}}}angular.module("draganddrop",[]).directive("draggable",["$parse",draggableDirective]).directive("drop",["$parse",dropDirective]); +function draggableDirective(a){return{restrict:"A",link:function(b,c,d){function e(){try{return b.$eval(i)}catch(a){throw new Error("draggable-data can't be parsed by Angular : check your draggable directive invocation !")}}function f(a){a.dataTransfer.effectAllowed=h||a.dataTransfer.effectAllowed;var c=e(),d=angular.toJson(c);b.$apply(function(){l(b,{$data:c,$event:a})}),a.dataTransfer.setData("json/"+j,d),a.stopPropagation()}function g(a){var c=e();b.$apply(function(){m(b,{$data:c,$event:a})}),a.stopPropagation()}var h,i,j,k=c[0];d.$observe("effectAllowed",function(a){h=a}),d.$observe("draggableData",function(a){i=a}),d.$observe("draggableType",function(a){j=a});var l=a(d.dragStart),m=a(d.dragEnd);d.$observe("draggable",function(a){k.draggable="false"!==a}),k.addEventListener("dragstart",f),k.addEventListener("dragend",g),b.$on("$destroy",function(){k.removeEventListener("dragstart",f),k.removeEventListener("dragend",g)})}}}function dropDirective(a){return{restrict:"A",link:function(b,c,d){function e(a){if(!j(b.$eval(o),a))return!0;a.dataTransfer.dropEffect=n||a.dataTransfer.dropEffect,a.preventDefault(),p&&c.addClass(p);var e=(new Date).getTime();if(!(200>e-u)&&(u=e,d.dragOver)){var f=k(a);b.$apply(function(){q(b,{$data:f,$event:a})})}}function f(a){if(!j(b.$eval(o),a))return!0;if(a.preventDefault(),d.dragEnter){var c=k(a);b.$apply(function(){r(b,{$data:c,$event:a})})}}function g(a){if(!j(b.$eval(o),a))return!0;if(a.preventDefault(),i(),d.dragLeave){var c=k(a);b.$apply(function(){s(b,{$data:c,$event:a})})}}function h(a){var c=k(a);i(),b.$apply(function(){t(b,{$data:c,$event:a})}),a.preventDefault()}function i(){c.removeClass(p)}function j(a,b){return"boolean"==typeof a?a:"string"==typeof a?j([a],b):Array.isArray(a)?j(function(b){return b.some(function(b){return b=b.split("/"),a.some(function(a){a=a.split("/");var c=!0;return b.forEach(function(b,d){"*"!==a[d]&&b!==a[d]&&(c=!1)}),c})})},b):"function"==typeof a?a(l(b.dataTransfer.types)):!1}function k(a){var b=l(a.dataTransfer.types);return b.reduce(function(b,c){var d=a.dataTransfer.getData(c),e=c.split("/")[0];return"json"===e&&d&&(d=angular.fromJson(d)),b[c]=d,c.split("/").reduce(function(a,c){return a.push(c),b[a.join("/")]=d,a},[]),b},{})}function l(a){return Array.prototype.slice.call(a)}var m=c[0],n=d.dropEffect,o=d.dropAccept,p=d.dragOverClass,q=a(d.dragOver),r=a(d.dragEnter),s=a(d.dragLeave),t=a(d.drop);m.addEventListener("dragover",e),m.addEventListener("dragenter",f),m.addEventListener("dragleave",g),m.addEventListener("drop",h),b.$on("$destroy",function(){m.removeEventListener("dragover",e),m.removeEventListener("dragenter",f),m.removeEventListener("dragleave",g),m.removeEventListener("drop",h)});var u=0}}}angular.module("draganddrop",[]).directive("draggable",["$parse",draggableDirective]).directive("drop",["$parse",dropDirective]); //# sourceMappingURL=angular-draganddrop.min.map \ No newline at end of file diff --git a/angular-draganddrop.min.map b/angular-draganddrop.min.map index ec393b3..6f1a096 100644 --- a/angular-draganddrop.min.map +++ b/angular-draganddrop.min.map @@ -1 +1 @@ -{"version":3,"file":"angular-draganddrop.min.js","sources":["angular-draganddrop.js"],"names":["draggableDirective","$parse","restrict","link","scope","element","attrs","dragStartListener","event","dataTransfer","effectAllowed","data","$eval","draggableData","jsonData","angular","toJson","$apply","dragStartHandler","$data","$event","setData","draggableType","stopPropagation","dragEndListener","dragEndHandler","domElement","$observe","val","dragStart","dragEnd","draggable","addEventListener","$on","removeEventListener","dropDirective","dragOverListener","accepts","dropAccept","dragOverClass","addClass","dropEffect","preventDefault","now","Date","getTime","throttledDragover","dragOver","getData","dragOverHandler","dragLeaveListener","removeDragOverClass","dragLeave","dragLeaveHandler","dropListener","dropHandler","removeClass","type","Array","isArray","eventTypes","some","eventType","split","_type","match","forEach","eventTypeChunk","index","toArray","types","reduce","collection","format","fromJson","accumulator","chunk","push","join","prototype","slice","call","drop","module","directive"],"mappings":";AAuBA,QAASA,oBAAmBC,GAC1B,OACEC,SAAU,IACVC,KAAM,SAAUC,EAAOC,EAASC,GAiC9B,QAASC,GAAkBC,GAEzBA,EAAMC,aAAaC,cAAgBA,GAAiBF,EAAMC,aAAaC,aAGvE,IAAIC,GAAOP,EAAMQ,MAAMC,GACnBC,EAAWC,QAAQC,OAAOL,EAG9BP,GAAMa,OAAO,WACXC,EAAiBd,GAASe,MAAOR,EAAMS,OAAQZ,MAIjDA,EAAMC,aAAaY,QAAQ,QAAUC,EAAeR,GAEpDN,EAAMe,kBAGR,QAASC,GAAgBhB,GAEvB,GAAIG,GAAOP,EAAMQ,MAAMC,EAGvBT,GAAMa,OAAO,WACXQ,EAAerB,GAASe,MAAOR,EAAMS,OAAQZ,MAG/CA,EAAMe,kBA5DR,GACIb,GACAG,EACAS,EAHAI,EAAarB,EAAQ,EAKzBC,GAAMqB,SAAS,gBAAiB,SAAUC,GACxClB,EAAgBkB,IAGlBtB,EAAMqB,SAAS,gBAAiB,SAAUC,GACxCf,EAAgBe,IAGlBtB,EAAMqB,SAAS,gBAAiB,SAAUC,GACxCN,EAAgBM,GAGlB,IAAIV,GAAmBjB,EAAOK,EAAMuB,WAChCJ,EAAiBxB,EAAOK,EAAMwB,QAElCxB,GAAMqB,SAAS,YAAa,SAAUI,GACpCL,EAAWK,UAA0B,UAAdA,IAGzBL,EAAWM,iBAAiB,YAAazB,GACzCmB,EAAWM,iBAAiB,UAAWR,GAEvCpB,EAAM6B,IAAI,WAAY,WACpBP,EAAWQ,oBAAoB,YAAa3B,GAC5CmB,EAAWQ,oBAAoB,UAAWV,OAsDlD,QAASW,eAAclC,GACrB,OACEC,SAAU,IACVC,KAAM,SAAUC,EAAOC,EAASC,GAsB9B,QAAS8B,GAAiB5B,GAExB,IAAM6B,EAAQjC,EAAMQ,MAAM0B,GAAa9B,GAAQ,OAAO,CAElD+B,IAAelC,EAAQmC,SAASD,GAGpC/B,EAAMC,aAAagC,WAAaA,GAAcjC,EAAMC,aAAagC,WAGjEjC,EAAMkC,gBAEN,IAAIC,IAAM,GAAIC,OAAOC,SACrB,MAA8B,IAA1BF,EAAMG,KACVA,EAAoBH,EAEfrC,EAAMyC,UAAX,CAEA,GAAIpC,GAAOqC,EAAQxC,EAGnBJ,GAAMa,OAAO,WACXgC,EAAgB7C,GAASe,MAAOR,EAAMS,OAAQZ,OAIlD,QAAS0C,GAAkB1C,GAEzB,IAAM6B,EAAQjC,EAAMQ,MAAM0B,GAAa9B,GAAQ,OAAO,CAItD,IAFA2C,IAEK7C,EAAM8C,UAAX,CAEA,GAAIzC,GAAOqC,EAAQxC,EAGnBJ,GAAMa,OAAO,WACXoC,EAAiBjD,GAASe,MAAOR,EAAMS,OAAQZ,MAIjDA,EAAMkC,kBAGR,QAASY,GAAa9C,GACpB,GAAIG,GAAOqC,EAAQxC,EAEnB2C,KAGA/C,EAAMa,OAAO,WACXsC,EAAYnD,GAASe,MAAOR,EAAMS,OAAQZ,MAI5CA,EAAMkC,iBAOR,QAASS,KACP9C,EAAQmD,YAAYjB,GAWtB,QAASF,GAAQoB,EAAMjD,GACrB,MAAoB,iBAATiD,GAA2BA,EAClB,gBAATA,GAA0BpB,GAASoB,GAAOjD,GACjDkD,MAAMC,QAAQF,GACTpB,EAAQ,SAAUuB,GACvB,MAAOA,GAAWC,KAAK,SAAUC,GAE/B,MADAA,GAAYA,EAAUC,MAAM,KACrBN,EAAKI,KAAK,SAAUG,GACzBA,EAAQA,EAAMD,MAAM,IAEpB,IAAIE,IAAQ,CAMZ,OALAH,GAAUI,QAAQ,SAAUC,EAAgBC,GACrB,MAAjBJ,EAAMI,IACND,IAAmBH,EAAMI,KAAQH,GAAQ,KAGxCA,OAGVzD,GAEe,kBAATiD,GAA4BA,EAAKY,EAAQ7D,EAAMC,aAAa6D,SAEhE,EAUT,QAAStB,GAAQxC,GACf,GAAI8D,GAAQD,EAAQ7D,EAAMC,aAAa6D,MAEvC,OAAOA,GAAMC,OAAO,SAAUC,EAAYf,GAExC,GAAI9C,GAAOH,EAAMC,aAAauC,QAAQS,GAGlCgB,EAAShB,EAAKM,MAAM,KAAK,EAa7B,OAVe,SAAXU,GAAqB9D,IAAMA,EAAOI,QAAQ2D,SAAS/D,IAEvD6D,EAAWf,GAAQ9C,EAEnB8C,EAAKM,MAAM,KAAKQ,OAAO,SAAUI,EAAaC,GAG5C,MAFAD,GAAYE,KAAKD,GACjBJ,EAAWG,EAAYG,KAAK,MAAQnE,EAC7BgE,OAGFH,OAUX,QAASH,GAAQG,GACf,MAAOd,OAAMqB,UAAUC,MAAMC,KAAKT,GAjKpC,GAAI9C,GAAarB,EAAQ,GACrBoC,EAAanC,EAAMmC,WACnBH,EAAahC,EAAMgC,WACnBC,EAAgBjC,EAAMiC,cAEtBU,EAAkBhD,EAAOK,EAAMyC,UAC/BM,EAAmBpD,EAAOK,EAAM8C,WAChCG,EAActD,EAAOK,EAAM4E,KAE/BxD,GAAWM,iBAAiB,WAAYI,GACxCV,EAAWM,iBAAiB,YAAakB,GACzCxB,EAAWM,iBAAiB,OAAQsB,GAEpClD,EAAM6B,IAAI,WAAY,WACpBP,EAAWQ,oBAAoB,WAAYE,GAC3CV,EAAWQ,oBAAoB,YAAagB,GAC5CxB,EAAWQ,oBAAoB,OAAQoB,IAGzC,IAAIR,GAAoB,IAnI9B/B,QACCoE,OAAO,kBACPC,UAAU,aAAc,SAAUpF,qBAClCoF,UAAU,QAAS,SAAUjD"} \ No newline at end of file +{"version":3,"file":"angular-draganddrop.min.js","sources":["angular-draganddrop.js"],"names":["draggableDirective","$parse","restrict","link","scope","element","attrs","informativeDraggableDataEval","$eval","draggableData","e","Error","dragStartListener","event","dataTransfer","effectAllowed","data","jsonData","angular","toJson","$apply","dragStartHandler","$data","$event","setData","draggableType","stopPropagation","dragEndListener","dragEndHandler","domElement","$observe","val","dragStart","dragEnd","draggable","addEventListener","$on","removeEventListener","dropDirective","dragOverListener","accepts","dropAccept","dropEffect","preventDefault","dragOverClass","addClass","now","Date","getTime","throttledDragover","dragOver","getData","dragOverHandler","dragEnterListener","dragEnter","dragEnterHandler","dragLeaveListener","removeDragOverClass","dragLeave","dragLeaveHandler","dropListener","dropHandler","removeClass","type","Array","isArray","eventTypes","some","eventType","split","_type","match","forEach","eventTypeChunk","index","toArray","types","reduce","collection","format","fromJson","accumulator","chunk","push","join","prototype","slice","call","drop","module","directive"],"mappings":"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"} \ No newline at end of file diff --git a/package.json b/package.json index 371d0b9..6f2cbf3 100644 --- a/package.json +++ b/package.json @@ -4,8 +4,9 @@ "description": "Drag and drop directives for Angular using native HTML5 API.", "main": "index.js", "scripts": { - "install": "bower install", - "test": "./node_modules/karma/bin/karma start --single-run" + "postinstall": "bower install", + "test": "karma start --single-run", + "build": "grunt" }, "repository": { "type": "git", @@ -17,23 +18,23 @@ "angular", "directive" ], - "author": "Greg Bergé ", + "author": "Team CMS ", "license": "MIT", "bugs": { - "url": "https://github.com/neoziro/angular-draganddrop/issues" + "url": "https://github.com/lemonde/angular-draganddrop/issues" }, "devDependencies": { - "bower": "~1.3.3", - "chai": "~1.9.1", - "chai-jquery": "~1.2.3", - "grunt": "~0.4.1", + "bower": "~1.4", + "chai": "~3.2", + "chai-jquery": "~2.0", + "grunt": "~0.4.5", "grunt-contrib-uglify": "~0.2.2", "karma": "~0.12.12", - "karma-chrome-launcher": "~0.1.3", + "karma-chrome-launcher": "~0.2", "karma-firefox-launcher": "~0.1.3", - "karma-mocha": "~0.1.3", - "mocha": "~1.20.1", - "sinon": "~1.10.3", - "sinon-chai": "~2.5.0" + "karma-mocha": "~0.2", + "mocha": "~2.2", + "sinon": "~1.15", + "sinon-chai": "~2.8" } } diff --git a/test/draggable.js b/test/draggable.js index 1b69853..886d21b 100644 --- a/test/draggable.js +++ b/test/draggable.js @@ -1,79 +1,189 @@ +'use strict'; + var expect = chai.expect; describe('Draggable directive', function () { - var $compile, $rootScope, scope; + var $compile, $rootScope, $scope, element; + var testDragStartEvent, testDragEndEvent; beforeEach(module('draganddrop')); beforeEach(inject(function ($injector) { $compile = $injector.get('$compile'); $rootScope = $injector.get('$rootScope'); - scope = $rootScope.$new(); + $scope = $rootScope.$new(); })); - it('should enable drag', function () { - var element = $compile('
')(scope); - scope.$digest(); - expect(element).to.have.attr('draggable', 'true'); + beforeEach(function () { + var dragDataStore = {}; + + // simulated dragstart event + testDragStartEvent = document.createEvent('CustomEvent'); + testDragStartEvent.initCustomEvent('dragstart', true, false, false); + testDragStartEvent.dataTransfer = { + setData: function(format, data) { dragDataStore[format] = data; }, + getData: function(format) { return dragDataStore[format]; } + }; + sinon.spy(testDragStartEvent.dataTransfer, 'setData'); + sinon.spy(testDragStartEvent.dataTransfer, 'getData'); + + // simulated dragend event + testDragEndEvent = document.createEvent('CustomEvent'); + testDragEndEvent.initCustomEvent('dragend', true, false, false); + testDragEndEvent.dataTransfer = { + setData: function(format, data) { dragDataStore[format] = data; }, + getData: function(format) { return dragDataStore[format]; } + }; + sinon.spy(testDragEndEvent.dataTransfer, 'setData'); + sinon.spy(testDragEndEvent.dataTransfer, 'getData'); }); - it('should disable drag', function () { - var element = $compile('
')(scope); - expect(element).to.have.attr('draggable', 'false'); - }); + function dispatchEvent(element, event) { + element[0].dispatchEvent(event); + $scope.$digest(); + } - describe('width "effect-allowed", "draggable-data" and "draggable-type" attributes', function () { - var startDrag, dragEvent; + function createElement(tpl) { + element = $compile(tpl)($scope); + $scope.$digest(); + } - beforeEach(function () { - dragEvent = document.createEvent('CustomEvent'); - dragEvent.initCustomEvent('dragstart', true, false, false); - dragEvent.dataTransfer = { - setData: sinon.spy() - }; + describe('HTML5 draggable property', function() { + it('should be enabled by default', function () { + createElement('
'); + expect(element).to.have.attr('draggable', 'true'); + }); - startDrag = function (element) { - element[0].dispatchEvent(dragEvent); - }; + it('should be disabled when requested', function () { + createElement('
'); + expect(element).to.have.attr('draggable', 'false'); }); + }); + + describe('on dragstart when "effect-allowed", "draggable-data" and "draggable-type" attributes are set', function () { it('should set effect and data', function () { var tpl = '
'; - var element = $compile(tpl)(scope); + createElement(tpl); + + dispatchEvent(element, testDragStartEvent); + + expect(testDragStartEvent.dataTransfer.effectAllowed).to.equal('link'); + expect(testDragStartEvent.dataTransfer.setData).to.have.been.calledWith('json/image', '{"foo":"bar"}'); + }); - scope.$digest(); - startDrag(element); + describe('error handling', function() { + var originalHandler; + + // mandatory to test uncaught + beforeEach(function() { + originalHandler = window.onerror; + }); + afterEach(function() { + window.onerror = originalHandler; + }); + + it('on wrongly formatted draggable data, should throw a meaningful error', function (done) { + var tpl = '
'; + createElement(tpl); + + var uncaughtErrorMessage = null; + window.onerror = function (message) { + if (uncaughtErrorMessage) { // only handle one time to avoid error loops + console.log('uncaught error !', message); + return originalHandler.apply(null, arguments); + } + uncaughtErrorMessage = message || 'no message ?'; + return true; + }; + + dispatchEvent(element, testDragStartEvent); + + expect(uncaughtErrorMessage) + .to.have.string('draggable-data can\'t be parsed by Angular : check your draggable directive invocation !'); + done(); + }); - expect(dragEvent.dataTransfer.effectAllowed).to.equal('link'); - expect(dragEvent.dataTransfer.setData).to.be.calledWith('json/image', '{"foo":"bar"}'); }); it('should listen for data changes', function () { var tpl = '
'; - scope.dragData = {foo: 'bar'}; - var element = $compile(tpl)(scope); + $scope.dragData = {foo: 'bar'}; + createElement(tpl); - scope.dragData = { bar: 'baz'}; - scope.$digest(); - startDrag(element); + $scope.dragData = { bar: 'baz'}; + $scope.$digest(); + dispatchEvent(element, testDragStartEvent); - expect(dragEvent.dataTransfer.effectAllowed).to.equal('link'); - expect(dragEvent.dataTransfer.setData).to.be.calledWith('json/image', '{"bar":"baz"}'); + expect(testDragStartEvent.dataTransfer.effectAllowed).to.equal('link'); + expect(testDragStartEvent.dataTransfer.setData).to.have.been.calledWith('json/image', '{"bar":"baz"}'); }); it('should prevent bubbling', function() { - var tpl = '
'+ - '
'+ - '
'; - var element = $compile(tpl)(scope); + var tpl = '
'+ + '
'+ + '
'; + createElement(tpl); - // Bubbling can only be test on firefox, due to a chrome bug where customEvent don't bubble - startDrag(element.find('.sub')); + // Bubbling can only be tested on firefox, due to a chrome bug where customEvent doesn't bubble + dispatchEvent(element.find('.sub'), testDragStartEvent); - expect(dragEvent.dataTransfer.setData).to.have.been.calledOnce; + expect(testDragStartEvent.dataTransfer.setData).to.have.been.calledOnce; }); }); + + describe('handlers', function() { + + describe('dragstart', function() { + beforeEach(function() { + $scope.onDrag = sinon.spy(); + var tpl = '
'; + createElement(tpl); + + dispatchEvent(element, testDragStartEvent); + }); + + it('should be called on dragstart when present', function() { + expect($scope.onDrag).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({ + foo: 'bar' + }); + }); + }); + + describe('dragend', function() { + beforeEach(function() { + $scope.onDrag = sinon.spy(); + var tpl = '
'; + createElement(tpl); + + dispatchEvent(element, testDragStartEvent); + dispatchEvent(element, testDragEndEvent); + }); + + it('should be called on dragend when present', function() { + expect($scope.onDrag).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({ + foo: 'bar' + }); + }); + }); + }); + }); diff --git a/test/drop.js b/test/drop.js index 05c0d5c..113ae68 100644 --- a/test/drop.js +++ b/test/drop.js @@ -1,14 +1,28 @@ +'use strict'; + var expect = chai.expect; -describe('Drop directive', function () { - var $compile, $rootScope, scope, dragOver, dragLeave, dragOverEvent, drop, dropEvent; +describe.only('Drop directive', function () { + var $compile, $rootScope, $scope, element; + var dragOverEvent, dropEvent, dragEnterEvent, dragLeaveEvent; beforeEach(module('draganddrop')); beforeEach(inject(function ($injector) { $compile = $injector.get('$compile'); $rootScope = $injector.get('$rootScope'); - scope = $rootScope.$new(); + $scope = $rootScope.$new(); + })); + + beforeEach(function () { + var testTypes = [ 'json/image', 'text/uri-list' ]; + var dragDataStore = { + // pre-filled with test data + 'json/image': {foo: 'bar'}, + //'json': {foo: 'bar'}, + 'text/uri-list': 'http://dragdrop.com', + //'text': 'http://dragdrop.com'*/ + }; // "dragover" event. dragOverEvent = document.createEvent('CustomEvent'); @@ -16,11 +30,9 @@ describe('Drop directive', function () { sinon.spy(dragOverEvent, 'preventDefault'); dragOverEvent.dataTransfer = { dropEffect: 'none', - types: ['json/image', 'text/uri-list'], - getData: function (type) { - if (type === 'json/image') return '{"foo":"bar"}'; - if (type === 'text/uri-list') return 'http://dragdrop.com'; - } + types: testTypes, + setData: function(format, data) { dragDataStore[format] = data; }, + getData: function(format) { return dragDataStore[format]; } }; // "drop" event. @@ -28,129 +40,119 @@ describe('Drop directive', function () { dropEvent.initCustomEvent('drop', false, false, false); sinon.spy(dropEvent, 'preventDefault'); dropEvent.dataTransfer = { - types: ['json/image', 'text/uri-list'], - getData: function (type) { - if (type === 'json/image') return '{"foo":"bar"}'; - if (type === 'text/uri-list') return 'http://dragdrop.com'; - } + types: testTypes, + setData: function(format, data) { dragDataStore[format] = data; }, + getData: function(format) { return dragDataStore[format]; } + }; + + // "dragenter" event. + dragEnterEvent = document.createEvent('CustomEvent'); + dragEnterEvent.initCustomEvent('dragenter', false, false, false); + dragEnterEvent.dataTransfer = { + types: testTypes, + setData: function(format, data) { dragDataStore[format] = data; }, + getData: function(format) { return dragDataStore[format]; } }; // "dragleave" event. - var dragLeaveEvent = document.createEvent('CustomEvent'); + dragLeaveEvent = document.createEvent('CustomEvent'); dragLeaveEvent.initCustomEvent('dragleave', false, false, false); dragLeaveEvent.dataTransfer = { - types: ['json/image', 'text/uri-list'], - getData: function (type) { - if (type === 'json/image') return '{"foo":"bar"}'; - if (type === 'text/uri-list') return 'http://dragdrop.com'; - } - }; - - dragOver = function (element) { - element[0].dispatchEvent(dragOverEvent); + types: testTypes, + setData: function(format, data) { dragDataStore[format] = data; }, + getData: function(format) { return dragDataStore[format]; } }; + }); - dragLeave = function (element) { - element[0].dispatchEvent(dragLeaveEvent); - }; + function dispatchEvent(element, event) { + element[0].dispatchEvent(event); + $scope.$digest(); + } - drop = function (element) { - element[0].dispatchEvent(dropEvent); - }; - })); + function createElement(tpl) { + element = $compile(tpl)($scope); + $scope.$digest(); + } describe('"drop-effect"', function () { it('should set dataTransfer.dropEffect', function () { var tpl = '
'; - var element = $compile(tpl)(scope); + createElement(tpl); - dragOver(element); + dispatchEvent(element, dragOverEvent); expect(dragOverEvent.dataTransfer.dropEffect).to.equal('link'); }); }); - describe('"drag-over"', function () { - it('should call "drag-over" with $event', function () { - scope.onDragOver = sinon.spy(); - - var tpl = '
'; - var element = $compile(tpl)(scope); - - dragOver(element); - - expect(scope.onDragOver).to.be.calledWith(dragOverEvent); - }); - }); - describe('"drop-accept"', function () { describe('string', function () { it('should accept type', function () { var tpl = '
'; - var element = $compile(tpl)(scope); + createElement(tpl); - dragOver(element); + dispatchEvent(element, dragOverEvent); - expect(dragOverEvent.preventDefault).to.be.called; + expect(dragOverEvent.preventDefault).to.have.been.called; }); it('should not accept type', function () { var tpl = '
'; - var element = $compile(tpl)(scope); + createElement(tpl); - dragOver(element); + dispatchEvent(element, dragOverEvent); - expect(dragOverEvent.preventDefault).to.not.be.called; + expect(dragOverEvent.preventDefault).to.not.have.been.called; }); }); describe('array', function () { it('should accept type', function () { var tpl = '
'; - var element = $compile(tpl)(scope); + createElement(tpl); - dragOver(element); + dispatchEvent(element, dragOverEvent); - expect(dragOverEvent.preventDefault).to.be.called; + expect(dragOverEvent.preventDefault).to.have.been.called; }); it('should not accept type', function () { var tpl = '
'; - var element = $compile(tpl)(scope); + createElement(tpl); - dragOver(element); + dispatchEvent(element, dragOverEvent); - expect(dragOverEvent.preventDefault).to.not.be.called; + expect(dragOverEvent.preventDefault).to.not.have.been.called; }); }); describe('function', function () { it('should accept type', function () { - scope.checkType = function (types) { + $scope.checkType = function (types) { expect(types).to.eql(['json/image', 'text/uri-list']); return true; }; var tpl = '
'; - var element = $compile(tpl)(scope); + createElement(tpl); - dragOver(element); + dispatchEvent(element, dragOverEvent); - expect(dragOverEvent.preventDefault).to.be.called; + expect(dragOverEvent.preventDefault).to.have.been.called; }); it('should not accept type', function () { - scope.checkType = function (types) { + $scope.checkType = function (types) { expect(types).to.eql(['json/image', 'text/uri-list']); return false; }; var tpl = '
'; - var element = $compile(tpl)(scope); + createElement(tpl); - dragOver(element); + dispatchEvent(element, dragOverEvent); - expect(dragOverEvent.preventDefault).to.not.be.called; + expect(dragOverEvent.preventDefault).to.not.have.been.called; }); it('should be compatible with DOMStringList that are not array', function () { @@ -160,37 +162,37 @@ describe('Drop directive', function () { length: 2 }; - scope.checkType = function (types) { + $scope.checkType = function (types) { expect(types).to.eql(['json/image', 'text/uri-list']); return false; }; var tpl = '
'; - var element = $compile(tpl)(scope); + createElement(tpl); - dragOver(element); + dispatchEvent(element, dragOverEvent); - expect(dragOverEvent.preventDefault).to.not.be.called; + expect(dragOverEvent.preventDefault).to.not.have.been.called; }); }); describe('boolean', function () { it('should accept type', function () { var tpl = '
'; - var element = $compile(tpl)(scope); + createElement(tpl); - dragOver(element); + dispatchEvent(element, dragOverEvent); - expect(dragOverEvent.preventDefault).to.be.called; + expect(dragOverEvent.preventDefault).to.have.been.called; }); it('should not accept type', function () { var tpl = '
'; - var element = $compile(tpl)(scope); + createElement(tpl); - dragOver(element); + dispatchEvent(element, dragOverEvent); - expect(dragOverEvent.preventDefault).to.not.be.called; + expect(dragOverEvent.preventDefault).to.not.have.been.called; }); }); }); @@ -198,49 +200,121 @@ describe('Drop directive', function () { describe('"drag-over-class"', function () { it('should add class if accepted', function () { var tpl = '
'; - var element = $compile(tpl)(scope); + createElement(tpl); - dragOver(element); + dispatchEvent(element, dragOverEvent); expect(element).to.have.class('dragover'); }); it('should remove class on drag leave', function () { var tpl = '
'; - var element = $compile(tpl)(scope); + createElement(tpl); - dragOver(element); - dragLeave(element); + dispatchEvent(element, dragOverEvent); + dispatchEvent(element, dragLeaveEvent); expect(element).to.not.have.class('dragover'); }); it('should remove class on drop', function () { var tpl = '
'; - var element = $compile(tpl)(scope); + createElement(tpl); - dragOver(element); - drop(element); + dispatchEvent(element, dragOverEvent); + dispatchEvent(element, dropEvent); expect(element).to.not.have.class('dragover'); }); }); - describe('"drop"', function () { - it('should call "drop" with $data and $event', function () { - scope.onDrop = sinon.spy(); + describe('handlers', function() { + var expectedData = { + 'json/image': {foo: 'bar'}, + 'json': {foo: 'bar'}, + 'text/uri-list': 'http://dragdrop.com', + 'text': 'http://dragdrop.com' + }; + + describe('"drag-enter"', function() { + beforeEach(function() { + $scope.onDrag = sinon.spy(); + var tpl = '
'; + createElement(tpl); + + dispatchEvent(element, dragEnterEvent); + }); + + it('should be called on dragenter when present', function() { + expect($scope.onDrag).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); + }); + }); - var tpl = '
'; - var element = $compile(tpl)(scope); + describe('"drag-leave"', function() { + beforeEach(function() { + $scope.onDrag = sinon.spy(); + var tpl = '
'; + createElement(tpl); - drop(element); + dispatchEvent(element, dragLeaveEvent); + }); - expect(scope.onDrop).to.be.calledWith({ - 'json/image': {foo: 'bar'}, - 'json': {foo: 'bar'}, - 'text/uri-list': 'http://dragdrop.com', - 'text': 'http://dragdrop.com' - }, dropEvent); + it('should be called on dragleave when present', function() { + expect($scope.onDrag).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); + }); + }); + + describe('"drag-over"', function() { + beforeEach(function() { + $scope.onDrag = sinon.spy(); + var tpl = '
'; + createElement(tpl); + + dispatchEvent(element, dragOverEvent); + }); + + it('should be called on dragover when present', function() { + expect($scope.onDrag).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); + }); + }); + + describe('"drop"', function() { + beforeEach(function() { + $scope.onDrag = sinon.spy(); + var tpl = '
'; + createElement(tpl); + + dispatchEvent(element, dropEvent); + }); + + it('should be called on drop when present', function() { + expect($scope.onDrag).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); + }); }); }); + + describe('throttling', function() { + it('should limit the calls to the drag-over callback'); + }); + });