├── .gitignore ├── .travis.yml ├── bower.json ├── example ├── img │ ├── pict1.jpg │ ├── pict2.jpg │ ├── pict3.jpg │ ├── pict4.jpg │ ├── pict5.jpg │ └── pict6.jpg ├── index.html ├── main.js └── mainStyle.css ├── karma.conf.js ├── lrDragNDrop.js ├── package.json ├── readme.md └── test └── spec.js /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /bower_components 3 | /node_modules -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | 5 | services: mongodb 6 | 7 | before_script: 8 | - npm install -g bower 9 | - npm install 10 | - bower install 11 | 12 | script: 13 | - npm test -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lrDragNDrop", 3 | "version": "1.0.2", 4 | "dependencies": { 5 | "angular": "~1.2.25" 6 | }, 7 | "devDependencies": { 8 | "angular-mocks": "~1.2.25", 9 | "jquery": "~2.0.3" 10 | }, 11 | "homepage": "https://github.com/lorenzofox3/lrDragNDrop", 12 | "authors": [ 13 | "RENARD Laurent " 14 | ], 15 | "moduleType": [ 16 | "globals" 17 | ], 18 | "main": "lrDragNDrop.js", 19 | "ignore": [ 20 | "**/.*", 21 | "node_modules", 22 | "bower_components", 23 | "test" 24 | ] 25 | } -------------------------------------------------------------------------------- /example/img/pict1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lorenzofox3/lrDragNDrop/7c5aa0aa7631dd1cedd4c234e2b0f59b230e537f/example/img/pict1.jpg -------------------------------------------------------------------------------- /example/img/pict2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lorenzofox3/lrDragNDrop/7c5aa0aa7631dd1cedd4c234e2b0f59b230e537f/example/img/pict2.jpg -------------------------------------------------------------------------------- /example/img/pict3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lorenzofox3/lrDragNDrop/7c5aa0aa7631dd1cedd4c234e2b0f59b230e537f/example/img/pict3.jpg -------------------------------------------------------------------------------- /example/img/pict4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lorenzofox3/lrDragNDrop/7c5aa0aa7631dd1cedd4c234e2b0f59b230e537f/example/img/pict4.jpg -------------------------------------------------------------------------------- /example/img/pict5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lorenzofox3/lrDragNDrop/7c5aa0aa7631dd1cedd4c234e2b0f59b230e537f/example/img/pict5.jpg -------------------------------------------------------------------------------- /example/img/pict6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lorenzofox3/lrDragNDrop/7c5aa0aa7631dd1cedd4c234e2b0f59b230e537f/example/img/pict6.jpg -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | lrDragNDrop demo 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |

Non safe source ("lr-drag-src")

14 | 15 |

Drag from the left collection and drop into the right collection. This is somehow equivalent to a cut operation

16 | 17 |
18 |

source collection

19 |
    20 |
  • 21 |

    {{ item.title }}

    22 | 23 |
  • 24 |
25 |
model: 26 | 27 |
{{ collection | json }}
28 |
29 |
30 |
31 |

target collection

32 |
    33 |
  • 34 |

    {{ item.title }}

    35 | 36 |
  • 37 |
38 |
model: 39 | 40 |
{{ collectionBis | json }}
41 |
42 |
43 |
44 |
45 |

Safe source ("lr-drag-src-safe")

46 | 47 |

Drag from the left collection and drop into the right collection. This is somehow equivalent to a copy operation

48 | 49 |
50 |

source collection

51 |
    52 |
  • 53 |

    {{ item.title }}

    54 | 55 |
  • 56 |
57 |
model: 58 | 59 |
{{ collection | json }}
60 |
61 |
62 |
63 |

target collection

64 |
    65 |
  • 66 |

    {{ item.title }}

    67 | 68 |
  • 69 |
70 |
model: 71 | 72 |
{{ collectionBis | json }}
73 |
74 |
75 |
76 |
77 |

When the target is also the source

78 | 79 |

Drag and drop the items to reorder them

80 | 81 |
82 |
    83 |
  • {{ item.title }}

    84 |
  • 85 |
86 |
model: 87 | 88 |
{{ collection | json }}
89 |
90 |
91 |
92 | 93 | -------------------------------------------------------------------------------- /example/main.js: -------------------------------------------------------------------------------- 1 | angular.module('app', ['lrDragNDrop']) 2 | .controller('safeCtrl', ['$scope', function (scope) { 3 | scope.collection = [ 4 | {src: 'img/pict1.jpg', title: 'picture 1'}, 5 | {src: 'img/pict2.jpg', title: 'picture 2'}, 6 | {src: 'img/pict3.jpg', title: 'picture 3'} 7 | ]; 8 | scope.collectionBis = [ 9 | {src: 'img/pict4.jpg', title: 'picture 4'}, 10 | {src: 'img/pict5.jpg', title: 'picture 5'}, 11 | {src: 'img/pict6.jpg', title: 'picture 6'} 12 | ]; 13 | }]) 14 | .controller('nonSafeCtrl', ['$scope', function (scope) { 15 | scope.collection = [ 16 | {src: 'img/pict1.jpg', title: 'picture 1'}, 17 | {src: 'img/pict2.jpg', title: 'picture 2'}, 18 | {src: 'img/pict3.jpg', title: 'picture 3'} 19 | ]; 20 | scope.collectionBis = [ 21 | {src: 'img/pict4.jpg', title: 'picture 4'}, 22 | {src: 'img/pict5.jpg', title: 'picture 5'}, 23 | {src: 'img/pict6.jpg', title: 'picture 6'} 24 | ]; 25 | scope.dropSuccess = function(e, item, collection) { 26 | console.log('dropSuccess'); 27 | console.log(e, item, collection); 28 | }; 29 | }]) 30 | .controller('reorderCtrl', ['$scope', function (scope) { 31 | scope.collection = [ 32 | {src: 'img/pict1.jpg', title: 'picture 1'}, 33 | {src: 'img/pict2.jpg', title: 'picture 2'}, 34 | {src: 'img/pict3.jpg', title: 'picture 3'} 35 | ]; 36 | }]); 37 | -------------------------------------------------------------------------------- /example/mainStyle.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: asans-serif, sans-serif; 3 | background: rgb(80, 80, 80); 4 | color: rgb(220, 220, 220); 5 | } 6 | 7 | ul { 8 | width: 500px; 9 | display: inline-block; 10 | list-style: none; 11 | margin: 0; 12 | padding: 10px; 13 | } 14 | 15 | li { 16 | position: relative; 17 | } 18 | 19 | h1, h2, h3, h4, h5 { 20 | margin: 0; 21 | } 22 | 23 | h2 { 24 | padding: 10px; 25 | background: rgb(30, 30, 30); 26 | color: rgb(240, 240, 240); 27 | } 28 | 29 | section { 30 | box-sizing: border-box; 31 | margin-bottom: 15px; 32 | width: 100%; 33 | text-align: center; 34 | } 35 | 36 | .model { 37 | padding: 15px; 38 | background: lightgray; 39 | border: 2px solid rgb(30, 30, 30); 40 | color: rgb(80, 80, 80); 41 | box-shadow: inset 0px 0px 8px 0px rgb(160, 160, 160); 42 | } 43 | 44 | .source, .target { 45 | width: 40%; 46 | display: inline-block; 47 | vertical-align: top; 48 | } 49 | 50 | .lr-drop-target-after { 51 | border-bottom: 2px solid orange; 52 | } 53 | 54 | .lr-drop-target-before { 55 | border-top: 2px solid orange; 56 | } 57 | 58 | .lr-drop-target-after:before, .lr-drop-target-before:before { 59 | position: absolute; 60 | content: ''; 61 | border: 5px solid transparent; 62 | border-left-color: orange; 63 | display: inline-block; 64 | } 65 | 66 | .lr-drop-target-after:before { 67 | z-index: 10; 68 | left: -5px; 69 | bottom: -6px; 70 | } 71 | 72 | .lr-drop-target-before:before { 73 | z-index: 10; 74 | left: -5px; 75 | top: -6px; 76 | } 77 | 78 | .lr-drop-target-after:after, .lr-drop-target-before:after { 79 | position: absolute; 80 | content: ''; 81 | border: 5px solid transparent; 82 | border-right-color: orange; 83 | display: inline-block; 84 | } 85 | 86 | .lr-drop-target-after:after { 87 | right: -5px; 88 | bottom: -6px; 89 | } 90 | 91 | .lr-drop-target-before:after { 92 | right: -5px; 93 | top: -6px; 94 | } 95 | 96 | img { 97 | max-width: 240px; 98 | max-height: 160px; 99 | } 100 | 101 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // Karma configuration 4 | // Generated on Wed Jan 01 2014 20:31:07 GMT+0100 (CET) 5 | 6 | module.exports = function (config) { 7 | config.set({ 8 | 9 | // base path, that will be used to resolve files and exclude 10 | basePath: '', 11 | 12 | 13 | // frameworks to use 14 | frameworks: ['jasmine'], 15 | 16 | 17 | // list of files / patterns to load in the browser 18 | files: [ 19 | 'bower_components/jquery/jquery.js', 20 | 'bower_components/angular/angular.js', 21 | 'bower_components/angular-mocks/angular-mocks.js', 22 | 'lrDragNDrop.js', 23 | 'test/*.js' 24 | ], 25 | 26 | 27 | // list of files to exclude 28 | exclude: [ 29 | 30 | ], 31 | 32 | 33 | // test results reporter to use 34 | // possible values: 'dots', 'progress', 'junit', 'growl', 'coverage' 35 | reporters: ['progress'], 36 | 37 | 38 | // web server port 39 | port: 9876, 40 | 41 | 42 | // enable / disable colors in the output (reporters and logs) 43 | colors: true, 44 | 45 | 46 | // level of logging 47 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 48 | logLevel: config.LOG_INFO, 49 | 50 | 51 | // enable / disable watching file and executing tests whenever any file changes 52 | autoWatch: true, 53 | 54 | 55 | // Start these browsers, currently available: 56 | // - Chrome 57 | // - ChromeCanary 58 | // - Firefox 59 | // - Opera (has to be installed with `npm install karma-opera-launcher`) 60 | // - Safari (only Mac; has to be installed with `npm install karma-safari-launcher`) 61 | // - PhantomJS 62 | // - IE (only Windows; has to be installed with `npm install karma-ie-launcher`) 63 | browsers: ['PhantomJS'], 64 | 65 | 66 | // If browser does not capture in given timeout [ms], kill it 67 | captureTimeout: 60000, 68 | 69 | 70 | // Continuous Integration mode 71 | // if true, it capture browsers, run tests and exit 72 | singleRun: true 73 | }); 74 | }; 75 | -------------------------------------------------------------------------------- /lrDragNDrop.js: -------------------------------------------------------------------------------- 1 | (function (ng) { 2 | 'use strict'; 3 | 4 | function isJqueryEventDataTransfer(){ 5 | return window.jQuery && (-1 == window.jQuery.event.props.indexOf('dataTransfer')); 6 | } 7 | 8 | if (isJqueryEventDataTransfer()) { 9 | window.jQuery.event.props.push('dataTransfer'); 10 | } 11 | 12 | var module = ng.module('lrDragNDrop', []); 13 | 14 | module.service('lrDragStore', ['$document', function (document) { 15 | 16 | var store = {}; 17 | 18 | this.hold = function hold(key, item, collectionFrom, safe) { 19 | store[key] = { 20 | item: item, 21 | collection: collectionFrom, 22 | safe: safe === true 23 | } 24 | }; 25 | 26 | this.get = function (namespace) { 27 | var 28 | modelItem = store[namespace], itemIndex; 29 | 30 | if (modelItem) { 31 | itemIndex = modelItem.collection.indexOf(modelItem.item); 32 | return modelItem.safe === true ? modelItem.item : modelItem.collection.splice(itemIndex, 1)[0]; 33 | } else { 34 | return null; 35 | } 36 | }; 37 | 38 | this.clean = function clean() { 39 | store = {}; 40 | }; 41 | 42 | this.isHolding = function (namespace) { 43 | return store[namespace] !== undefined; 44 | }; 45 | 46 | document.bind('dragend', this.clean); 47 | }]); 48 | 49 | module.service('lrDragHelper', function () { 50 | var th = this; 51 | 52 | th.parseRepeater = function(scope, attr) { 53 | var 54 | repeatExpression = attr.ngRepeat, 55 | match; 56 | 57 | if (!repeatExpression) { 58 | throw Error('this directive must be used with ngRepeat directive'); 59 | } 60 | match = repeatExpression.match(/^(.*\sin).(\S*)/); 61 | if (!match) { 62 | throw Error("Expected ngRepeat in form of '_item_ in _collection_' but got '" + 63 | repeatExpression + "'."); 64 | } 65 | 66 | return scope.$eval(match[2]); 67 | }; 68 | 69 | th.lrDragSrcDirective = function(store, safe) { 70 | return function compileFunc(el, iattr) { 71 | iattr.$set('draggable', true); 72 | return function linkFunc(scope, element, attr) { 73 | var 74 | collection, 75 | key = (safe === true ? attr.lrDragSrcSafe : attr.lrDragSrc ) || 'temp'; 76 | 77 | if(attr.lrDragData) { 78 | scope.$watch(attr.lrDragData, function (newValue) { 79 | collection = newValue; 80 | }); 81 | } else { 82 | collection = th.parseRepeater(scope, attr); 83 | } 84 | 85 | element.bind('dragstart', function (evt) { 86 | store.hold(key, collection[scope.$index], collection, safe); 87 | if(angular.isDefined(evt.dataTransfer)) { 88 | evt.dataTransfer.setData('text/html', null); //FF/jQuery fix 89 | } 90 | }); 91 | } 92 | } 93 | } 94 | }); 95 | 96 | module.directive('lrDragSrc', ['lrDragStore', 'lrDragHelper', function (store, dragHelper) { 97 | return{ 98 | compile: dragHelper.lrDragSrcDirective(store) 99 | }; 100 | }]); 101 | 102 | module.directive('lrDragSrcSafe', ['lrDragStore', 'lrDragHelper', function (store, dragHelper) { 103 | return{ 104 | compile: dragHelper.lrDragSrcDirective(store, true) 105 | }; 106 | }]); 107 | 108 | module.directive('lrDropTarget', ['lrDragStore', 'lrDragHelper', '$parse', function (store, dragHelper, $parse) { 109 | return { 110 | link: function (scope, element, attr) { 111 | 112 | var 113 | collection, 114 | key = attr.lrDropTarget || 'temp', 115 | classCache = null; 116 | 117 | function isAfter(x, y) { 118 | //check if below or over the diagonal of the box element 119 | return (element[0].offsetHeight - x * element[0].offsetHeight / element[0].offsetWidth) < y; 120 | } 121 | 122 | function resetStyle() { 123 | if (classCache !== null) { 124 | element.removeClass(classCache); 125 | classCache = null; 126 | } 127 | } 128 | 129 | if(attr.lrDragData) { 130 | scope.$watch(attr.lrDragData, function (newValue) { 131 | collection = newValue; 132 | }); 133 | } else { 134 | collection = dragHelper.parseRepeater(scope, attr); 135 | } 136 | 137 | element.bind('drop', function (evt) { 138 | var 139 | collectionCopy = ng.copy(collection), 140 | item = store.get(key), 141 | dropIndex, i, l; 142 | if (item !== null) { 143 | dropIndex = scope.$index; 144 | dropIndex = isAfter(evt.offsetX, evt.offsetY) ? dropIndex + 1 : dropIndex; 145 | //srcCollection=targetCollection => we may need to apply a correction 146 | if (collectionCopy.length > collection.length) { 147 | for (i = 0, l = Math.min(dropIndex, collection.length - 1); i <= l; i++) { 148 | if (!ng.equals(collectionCopy[i], collection[i])) { 149 | dropIndex = dropIndex - 1; 150 | break; 151 | } 152 | } 153 | } 154 | scope.$apply(function () { 155 | collection.splice(dropIndex, 0, item); 156 | var fn = $parse(attr.lrDropSuccess) || ng.noop; 157 | fn(scope, {e: evt, item: item, collection: collection}); 158 | }); 159 | evt.preventDefault(); 160 | resetStyle(); 161 | store.clean(); 162 | } 163 | }); 164 | 165 | element.bind('dragleave', resetStyle); 166 | 167 | element.bind('dragover', function (evt) { 168 | var className; 169 | if (store.isHolding(key)) { 170 | className = isAfter(evt.offsetX, evt.offsetY) ? 'lr-drop-target-after' : 'lr-drop-target-before'; 171 | if (classCache !== className && classCache !== null) { 172 | element.removeClass(classCache); 173 | } 174 | if (classCache !== className) { 175 | element.addClass(className); 176 | } 177 | classCache = className; 178 | } 179 | evt.preventDefault(); 180 | }); 181 | } 182 | }; 183 | }]); 184 | })(angular); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lrDragNDrop", 3 | "author": "RENARD Laurent ", 4 | "description": "drag and drop module for Angularjs which allows to drag items from one collection and drop to another one", 5 | "main": "lrDragNDrop.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/lorenzofox3/lrDragNDrop" 9 | }, 10 | "keywords": [ 11 | "angular", 12 | "directive", 13 | "draganddrop" 14 | ], 15 | "scripts": { 16 | "test": "./node_modules/karma/bin/karma start" 17 | }, 18 | "contributors": [ 19 | { 20 | "name": "Pavlo Bogomolenko", 21 | "email": "bpsource@gmail.com" 22 | } 23 | ], 24 | "devDependencies": { 25 | "karma": "~0.12.28", 26 | "karma-chrome-launcher": "~0.1.5", 27 | "karma-jasmine": "~0.3.1", 28 | "karma-phantomjs-launcher": "^0.1.4" 29 | }, 30 | "dependencies": {}, 31 | "bugs": { 32 | "url": "https://github.com/lorenzofox3/lrDragNDrop/issues" 33 | }, 34 | "licenses": [ 35 | { 36 | "type": "MIT", 37 | "url": "https://github.com/lorenzofox3/lrDragNDrop" 38 | } 39 | ], 40 | "engines": { 41 | "node": "~0.8 || ~0.10" 42 | }, 43 | "version": "1.0.2" 44 | } 45 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/lorenzofox3/lrDragNDrop.svg?branch=master)](https://travis-ci.org/lorenzofox3/lrDragNDrop) 2 | 3 | # lrDragNDrop 4 | 5 | *lrDragNDrop* is a drag and drop module for *Angularjs* which allows to drag items from one collection and drop to another one; or reorder the items within the same collection. 6 | 7 | It is "item oriented" which implies: 8 | * the directives must be used with the standard ngRepeat directive 9 | * "adorners" can be added 10 | * works only with non empty collections 11 | * you may want to have a look at [angular-dragon-drop](https://github.com/btford/angular-dragon-drop) if you don't need this "item oriented" approach. 12 | 13 | 14 | It is about 150 lines of code and has no dependency to third party library (except the framework itself). 15 | 16 | See [demo](http://lorenzofox3.github.io/lrDragNDrop/). 17 | 18 | ## Getting Started 19 | 20 | Add the module to your application using standard angularjs module management: 21 | ```javascript 22 | angular.module('myApp',['lrDragNDrop']); 23 | ``` 24 | ### Drag Source Directives 25 | 26 | Add one the two following source directives to a repeated items collection to make a collection become a drag source: 27 | 28 | ```html 29 | 32 | ``` 33 | 34 | The model associated to the dragged item will be removed from the collection once it is dropped into a drop target with the same namespace (see below): 35 | 36 | ```html 37 | 40 | ``` 41 | 42 | The model associated will not be removed from the source collection and a copy of the reference will be added to the target collection. 43 | 44 | Note: if you don't specify a namespace, a "global" namespace will be assumed. 45 | 46 | ### Drop Target Directive 47 | 48 | ```html 49 | 52 | ``` 53 | The targetCollection will be able to get all the dragged items if there were taken from a source with the same namespace. 54 | 55 | Note a target can be its own source (if you want to use drag and drop to reorder the items). 56 | 57 | ### Drop Success Callback 58 | 59 | ```html 60 | 63 | ``` 64 | Registers custom callback function that will be called after item will be dropped. Has to be used with lr-drop-target together. 65 | 66 | ## Adorners 67 | 68 | When a source item is dragged over a target element and if they share the same namespace a class name is added to the target element following this rule: 69 | * ``lr-drop-target-before``, if the cursor position is above the diagonal going from the bottom left corner to the top right corner of the target element 70 | * ``lr-drop-target-after``,if the cursor position is below the diagonal going from the bottom left corner to the top right corner of the target element 71 | 72 | Note you can modify the source code quite easily to have something more elaborated to take the collection orientation into account. 73 | 74 | ## Empty Collection 75 | The directives are associated with the item elements and not with the collection container element. So if there is no element yet in the target collection you won't be able to use the drop target feature. 76 | However you can easily attach your own directive base on the ``lrDragStore`` service to the container element to support the drop for empty target collection. 77 | 78 | ## Performance Tips 79 | 80 | ```html 81 | 84 | ``` 85 | 86 | If you have rather big collection than please use lr-drag-data attribute together with ng-repeat. This will internally prevent 87 | parsing ng-repeat block and execution of $eval. 88 | 89 | ## Licence 90 | 91 | lrDragNDrop module is under MIT license: 92 | 93 | > Copyright (C) 2013 Laurent Renard. 94 | > 95 | > Permission is hereby granted, free of charge, to any person 96 | > obtaining a copy of this software and associated documentation files 97 | > (the "Software"), to deal in the Software without restriction, 98 | > including without limitation the rights to use, copy, modify, merge, 99 | > publish, distribute, sublicense, and/or sell copies of the Software, 100 | > and to permit persons to whom the Software is furnished to do so, 101 | > subject to the following conditions: 102 | > 103 | > The above copyright notice and this permission notice shall be 104 | > included in all copies or substantial portions of the Software. 105 | > 106 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 107 | > EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 108 | > MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 109 | > NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 110 | > BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 111 | > ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 112 | > CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 113 | > SOFTWARE. 114 | -------------------------------------------------------------------------------- /test/spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | describe('lrDragNDrop', function () { 4 | 5 | describe('lrDragStore', function () { 6 | 7 | var 8 | service, 9 | defaultItem = {id: 'test'}, 10 | defaultCollection = [defaultItem]; 11 | 12 | beforeEach(module('lrDragNDrop')); 13 | 14 | beforeEach(inject(function (lrDragStore) { 15 | service = lrDragStore; 16 | })); 17 | 18 | it('should store an item base on passed parameters and return it', function () { 19 | service.hold('validKey', defaultItem, defaultCollection, true); 20 | expect(service.get('validKey')).toBe(defaultItem); 21 | }); 22 | 23 | it('should return null if there is nothing stored under a given namespace', function () { 24 | service.hold('validKey', defaultItem, defaultCollection, true); 25 | expect(service.get('whatever')).toBe(null); 26 | }); 27 | 28 | it('should not modify the original collection in safe mode', function () { 29 | var item; 30 | service.hold('validKey', defaultItem, defaultCollection, true); 31 | item = service.get('validKey'); 32 | expect(item).toBe(defaultItem); 33 | expect(defaultCollection).toEqual([defaultItem]); 34 | }); 35 | 36 | it('should remove the item from original collection when it is in unsafe mode', function () { 37 | var item; 38 | service.hold('validKey', defaultItem, defaultCollection, false); 39 | item = service.get('validKey'); 40 | expect(item).toBe(defaultItem); 41 | expect(defaultCollection).toEqual([]); 42 | }); 43 | 44 | it('should clean the whole store', function () { 45 | service.hold('validKey', defaultItem, defaultCollection, true); 46 | expect(service.get('validKey')).toBe(defaultItem); 47 | expect(service.get('validKey')).toBe(defaultItem); 48 | service.clean(); 49 | expect(service.get('validKey')).toBe(null); 50 | }); 51 | 52 | it('should tells if service is currently holding item under namespace', function () { 53 | service.hold('validKey', defaultItem, defaultCollection, true); 54 | expect(service.isHolding('validKey')).toBe(true); 55 | expect(service.isHolding('whatever')).toBe(false); 56 | }); 57 | 58 | }); 59 | 60 | 61 | describe('directives', function () { 62 | 63 | var 64 | compile, 65 | scope; 66 | 67 | 68 | describe('src directives', function () { 69 | 70 | var storeMock = { 71 | hold: angular.noop 72 | }, 73 | items = [ 74 | {id: 1}, 75 | {id: 2}, 76 | {id: 3} 77 | ]; 78 | 79 | beforeEach(module('lrDragNDrop', function ($provide) { 80 | $provide.factory('lrDragStore', function () { 81 | return storeMock; 82 | }); 83 | })); 84 | 85 | beforeEach(inject(function ($compile, $rootScope) { 86 | compile = $compile; 87 | scope = $rootScope; 88 | scope.items = items; 89 | })); 90 | 91 | it('should throw error if not used with ngRepeat', function () { 92 | try { 93 | compile('
  • ')(scope); 94 | } catch (e) { 95 | expect(e.message).toEqual('this directive must be used with ngRepeat directive'); 96 | } 97 | }); 98 | 99 | it('should add the draggable attribute to the element', function () { 100 | var 101 | el, itemEl; 102 | spyOn(storeMock, 'hold'); 103 | el = compile('')(scope); 104 | scope.$digest(); 105 | expect(el.children().length).toBe(3); 106 | itemEl = $(el.children()[1]); 107 | expect(itemEl[0].draggable).toBe(true); 108 | }); 109 | 110 | it('should store item on dragstart', function () { 111 | var 112 | el, itemEl; 113 | spyOn(storeMock, 'hold'); 114 | el = compile('')(scope); 115 | scope.$digest(); 116 | expect(el.children().length).toBe(3); 117 | itemEl = $(el.children()[1]); 118 | itemEl.triggerHandler('dragstart'); 119 | expect(storeMock.hold).toHaveBeenCalledWith('test', items[1], items, undefined); 120 | }); 121 | 122 | it('should store item safely on dragstart', function () { 123 | var 124 | el, itemEl; 125 | spyOn(storeMock, 'hold'); 126 | el = compile('')(scope); 127 | scope.$digest(); 128 | expect(el.children().length).toBe(3); 129 | itemEl = $(el.children()[1]); 130 | itemEl.triggerHandler('dragstart'); 131 | expect(storeMock.hold).toHaveBeenCalledWith('test', items[1], items, true); 132 | }); 133 | }); 134 | 135 | describe('target directive', function () { 136 | 137 | var items, itemsBis; 138 | 139 | beforeEach(module('lrDragNDrop')); 140 | 141 | beforeEach(inject(function ($compile, $rootScope) { 142 | compile = $compile; 143 | scope = $rootScope; 144 | items = [ 145 | {id: 1}, 146 | {id: 2}, 147 | {id: 3} 148 | ]; 149 | itemsBis = [ 150 | {id: 4}, 151 | {id: 5}, 152 | {id: 6} 153 | ]; 154 | scope.items = items; 155 | scope.itemsBis = itemsBis; 156 | })); 157 | 158 | it('should throw error if not used with ngRepeat', function () { 159 | try { 160 | compile('
  • ')(scope); 161 | } catch (e) { 162 | expect(e.message).toEqual('this directive must be used with ngRepeat directive'); 163 | } 164 | }); 165 | 166 | it('should drop the item at correct index without removing item', inject(function (lrDragStore) { 167 | var 168 | el, itemEl; 169 | el = compile('')(scope); 170 | scope.$digest(); 171 | expect(el.children().length).toBe(3); 172 | lrDragStore.hold('test', scope.items[0], scope.items, true); 173 | itemEl = $(el.children()[1]); 174 | itemEl.triggerHandler('drop'); 175 | expect(el.children().length).toBe(4); 176 | expect(scope.items.length).toBe(3); 177 | expect(scope.itemsBis[1]).toBe(scope.items[0]); 178 | })); 179 | 180 | it('should drop the item at correct index and remove it from original source', inject(function (lrDragStore) { 181 | var 182 | el, itemEl; 183 | el = compile('')(scope); 184 | scope.$digest(); 185 | expect(el.children().length).toBe(3); 186 | lrDragStore.hold('test', scope.items[0], scope.items, false); 187 | itemEl = $(el.children()[1]); 188 | itemEl.triggerHandler('drop'); 189 | expect(el.children().length).toBe(4); 190 | expect(scope.items.length).toBe(2); 191 | expect(scope.itemsBis[1].id).toBe(1); 192 | })); 193 | 194 | it('should drop the item at the correct index when source is target', inject(function (lrDragStore) { 195 | var 196 | el, itemEl; 197 | el = compile('')(scope); 198 | scope.$digest(); 199 | expect(el.children().length).toBe(3); 200 | lrDragStore.hold('test', scope.items[0], scope.items, false); 201 | itemEl = $(el.children()[2]); 202 | itemEl.triggerHandler('drop'); 203 | expect(el.children().length).toBe(3); 204 | expect(scope.items.length).toBe(3); 205 | expect(scope.items[1].id).toBe(1); 206 | })); 207 | 208 | it('should drop the item at the correct index when source is target', inject(function (lrDragStore) { 209 | var 210 | el, itemEl; 211 | el = compile('')(scope); 212 | scope.$digest(); 213 | expect(el.children().length).toBe(3); 214 | lrDragStore.hold('test', scope.items[2], scope.items, false); 215 | itemEl = $(el.children()[1]); 216 | itemEl.triggerHandler('drop'); 217 | expect(el.children().length).toBe(3); 218 | expect(scope.items.length).toBe(3); 219 | expect(scope.items[1].id).toBe(3); 220 | })); 221 | 222 | }); 223 | }); 224 | 225 | 226 | }); --------------------------------------------------------------------------------