├── .gitignore
├── README.md
├── bower.json
├── build
├── css
│ └── drag-and-drop.css
└── js
│ ├── angular-drag-and-drop.js
│ └── angular-drag-and-drop.min.js
├── examples
├── index.html
├── js
│ └── sample-app.js
└── vendor
│ └── angular
│ └── angular.min.js
├── gulpfile.coffee
├── package.json
└── src
├── app
└── angular-drag-and-drop.coffee
└── less
└── drag-and-drop.less
/.gitignore:
--------------------------------------------------------------------------------
1 | bower_components/
2 | .DS_Store
3 | node_modules/
4 | **/*/.DS_Store
5 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Angular Drag and Drop
2 | Customizable drag and drop behaviour for Angular.
3 |
4 | ### [Demo](http://lane.github.io/angular-drag-and-drop/)
5 |
6 | ## Getting Started
7 |
8 | ### Bower
9 | bower install angular-drag-and-drop-directives
10 |
11 | Or download the files from github, then:
12 |
13 | **In your HTML:**
14 |
15 | 1. Add `angular-drag-and-drop.js`
16 | 2. Add `angular-drag-and-drop.css`
17 | 3. Add required markup
18 |
19 | ```html
20 |
21 |
22 |
23 | Anything in here
24 | will be draggable
25 |
26 |
27 |
28 | Drop Here
29 |
30 |
31 |
32 | ```
33 |
34 | **In your Angular module:**
35 |
36 | 1. Add `"laneolson.ui.dragdrop"` as a dependency to the module.
37 | 2. In your controller, add functions for handling various events in the drag and drop.
38 |
39 | ## Customizing
40 |
41 | ### Directive Attributes
42 | You can pass various functions and options to the directive by assigning the following attributes:
43 |
44 | #### `drag-and-drop` - element or attribute
45 | - `on-drag-start`: function - fired when an item starts being dragged
46 | - `on-drag-end`: function - fired when an item is released
47 | - `on-drag-enter`: function - fired when an item is dragged over a drop spot
48 | - `on-drag-leave`: function - fired when an item is dragged outside of a drop spot
49 | - `on-item-placed`: function - fired when an item is dropped inside of a drop spot
50 | - `on-item-removed`: function - fired when and item is removed from its drop spot
51 | - `enable-swap`: boolean - an item will be swapped out when dropping a drag item on to a drop spot that has reached its maximum number of items
52 | - `fixed-positions`: boolean - when set to true, items return to their start positions when dropped outside of a drop spot
53 |
54 | #### `drag-item` - element or attribute
55 | - `drag-id`: an identifier that is used for this drag item. When set, the `drag-item` element will have a class that matches the `drag-id`.
56 | - `drag-data`: object - use to associate any additional data you may want for the draggable item.
57 | - `drop-to`: string - used to position the element within the drop spot (e.g. "top", or "bottom left")
58 | - `x`: int - the pixel x offset of the drag item from it's original position
59 | - `y`: int - the pixel y offset of the drag item from it's original position
60 | - `clone` : boolean - a clone item is dragged instead of the original item
61 | - `lock-vertical`: boolean - locks the item so it may only be moved left and right
62 |
63 | #### `drop-spot` - element or attribute
64 | - `drop-id`: string - an identifier that is used for this drop item. When set, the `drop-spot` element will have a class that matches the `drag-id`.
65 | - `max-items`: int - Used to specify the maximum number of items allowed in this drop spot
66 |
67 | ### Classes
68 | The following classes are added to elements to allow styling based on the current state of the drag and drop.
69 |
70 | - `dragging`: Added to the `drag-and-drop` wrapper when an item is being dragged.
71 | - `drop-hovering`: Added to an `drop-spot` element when an item has been dragged over top of the drop spot.
72 | - `drop-full`: Added to an `drop-spot` when it has reached its maximum number of items (assigned through `max-items`)
73 | - `drag-active`: added to a `drag-item` element when it is being dragged
74 | - `{{DRAG_ID}}`: added to an `drag-item` element if the `drag-id` attribute is set
75 | - `{{DROP_ID}}`: added to an `drop-spot` element if the `drop-id` attribute is set. An `drag-item` element will be given the class `in-{{DROP_ID}}` when it is placed within that drop spot.
76 |
77 | ## Todo
78 |
79 | - proper z-indexing when lifting and placing drag items
80 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular-drag-and-drop-directives",
3 | "description": "angular directives that provide drag and drop behaviour with ability to specify callbacks for certain actions",
4 | "main": "build/js/angular-drag-and-drop.js",
5 | "authors": [
6 | "Lane Olson"
7 | ],
8 | "license": "ISC",
9 | "keywords": [
10 | "angular",
11 | "drag",
12 | "drop"
13 | ],
14 | "homepage": "https://github.com/Lane/angular-drag-and-drop",
15 | "moduleType": [],
16 | "ignore": [
17 | "**/.*",
18 | "examples",
19 | "node_modules",
20 | "bower_components",
21 | "test",
22 | "tests"
23 | ],
24 | "dependencies": {
25 | "angular": "*"
26 | },
27 | "version": "1.0.4"
28 | }
29 |
--------------------------------------------------------------------------------
/build/css/drag-and-drop.css:
--------------------------------------------------------------------------------
1 | .drag-return drag-item,
2 | .drag-return .drag-item {
3 | -webkit-transition: all ease-in-out 0.5s;
4 | transition: all ease-in-out 0.5s;
5 | }
6 | drag-item,
7 | [drag-item],
8 | .drag-item {
9 | position: relative;
10 | -webkit-user-select: none;
11 | -moz-user-select: none;
12 | -ms-user-select: none;
13 | user-select: none;
14 | cursor: -webkit-grab;
15 | cursor: grab;
16 | z-index: 10;
17 | }
18 | drag-item:active,
19 | [drag-item]:active,
20 | .drag-item:active {
21 | cursor: -webkit-grabbing;
22 | cursor: grabbing;
23 | }
24 | .drag-content {
25 | -webkit-transform: scale(1);
26 | -ms-transform: scale(1);
27 | transform: scale(1);
28 | -webkit-transition: all 0.1s ease-in-out;
29 | transition: all 0.1s ease-in-out;
30 | }
31 | .drag-active {
32 | z-index: 20;
33 | }
34 | .clone {
35 | opacity: 0;
36 | z-index: 0;
37 | }
38 | .clone-active {
39 | opacity: 1;
40 | z-index: 9999;
41 | }
42 |
--------------------------------------------------------------------------------
/build/js/angular-drag-and-drop.js:
--------------------------------------------------------------------------------
1 | var module;
2 |
3 | module = angular.module("laneolson.ui.dragdrop", []);
4 |
5 | module.directive('dragAndDrop', [
6 | '$document', function($document) {
7 | return {
8 | restrict: 'AE',
9 | scope: {
10 | onItemPlaced: "&",
11 | onItemRemoved: "&",
12 | onDrag: "&",
13 | onDragStart: "&",
14 | onDragEnd: "&",
15 | onDragEnter: "&",
16 | onDragLeave: "&",
17 | enableSwap: "=",
18 | fixedPositions: "="
19 | },
20 | require: 'dragAndDrop',
21 | controller: [
22 | '$q', '$scope', function($q, $scope) {
23 | var currentDroppable, draggables, droppables, element, handlers, isInside, isIntersecting, isReady;
24 | $scope.draggables = draggables = [];
25 | $scope.droppables = droppables = [];
26 | $scope.isDragging = false;
27 | $scope.currentDraggable = null;
28 | currentDroppable = null;
29 | element = null;
30 | isReady = false;
31 | handlers = [];
32 | isInside = function(point, bounds) {
33 | var ref, ref1;
34 | return (bounds.left < (ref = point[0]) && ref < bounds.right) && (bounds.top < (ref1 = point[1]) && ref1 < bounds.bottom);
35 | };
36 | isIntersecting = function(r1, r2) {
37 | return !(r2.left > r1.right || r2.right < r1.left || r2.top > r1.bottom || r2.bottom < r1.top);
38 | };
39 | this.on = function(e, cb) {
40 | if (e === "ready" && isReady) {
41 | cb();
42 | }
43 | return handlers.push({
44 | name: e,
45 | cb: cb
46 | });
47 | };
48 | this.trigger = function(e) {
49 | var h, i, len, results;
50 | if (e === "ready") {
51 | isReady = true;
52 | }
53 | results = [];
54 | for (i = 0, len = handlers.length; i < len; i++) {
55 | h = handlers[i];
56 | if (h.name === e) {
57 | results.push(h.cb());
58 | } else {
59 | results.push(void 0);
60 | }
61 | }
62 | return results;
63 | };
64 | this.isReady = function() {
65 | return isReady;
66 | };
67 | this.setDragAndDropElement = function(el) {
68 | return element = el;
69 | };
70 | this.getDragAndDropElement = function() {
71 | return element;
72 | };
73 | this.checkForIntersection = function() {
74 | var dropSpot, i, len, results;
75 | results = [];
76 | for (i = 0, len = droppables.length; i < len; i++) {
77 | dropSpot = droppables[i];
78 | if (isInside($scope.currentDraggable.midPoint, dropSpot)) {
79 | if (!dropSpot.isActive) {
80 | this.setCurrentDroppable(dropSpot);
81 | dropSpot.activate();
82 | results.push(this.fireCallback('drag-enter'));
83 | } else {
84 | results.push(void 0);
85 | }
86 | } else {
87 | if (dropSpot.isActive) {
88 | this.setCurrentDroppable(null);
89 | dropSpot.deactivate();
90 | results.push(this.fireCallback('drag-leave'));
91 | } else {
92 | results.push(void 0);
93 | }
94 | }
95 | }
96 | return results;
97 | };
98 | this.setCurrentDraggable = function(draggable) {
99 | $scope.currentDraggable = draggable;
100 | if (draggable) {
101 | this.fireCallback('drag-start');
102 | }
103 | return $scope.$evalAsync(function() {
104 | $scope.currentDraggable = draggable;
105 | if (draggable) {
106 | return $scope.isDragging = true;
107 | } else {
108 | return $scope.isDragging = false;
109 | }
110 | });
111 | };
112 | this.getCurrentDraggable = function() {
113 | return $scope.currentDraggable;
114 | };
115 | this.setCurrentDroppable = function(droppable) {
116 | return currentDroppable = droppable;
117 | };
118 | this.getCurrentDroppable = function() {
119 | return currentDroppable;
120 | };
121 | this.addDroppable = function(droppable) {
122 | return droppables.push(droppable);
123 | };
124 | this.addDraggable = function(draggable) {
125 | return draggables.push(draggable);
126 | };
127 | this.fireCallback = function(type, e) {
128 | var state;
129 | state = {
130 | draggable: this.getCurrentDraggable(),
131 | droppable: this.getCurrentDroppable(),
132 | dragEvent: e
133 | };
134 | switch (type) {
135 | case 'drag-end':
136 | if (typeof $scope.onDragEnd === "function") {
137 | $scope.onDragEnd(state);
138 | }
139 | return this.trigger("drag-end");
140 | case 'drag-start':
141 | if (typeof $scope.onDragStart === "function") {
142 | $scope.onDragStart(state);
143 | }
144 | return this.trigger("drag-start");
145 | case 'drag':
146 | return typeof $scope.onDrag === "function" ? $scope.onDrag(state) : void 0;
147 | case 'item-assigned':
148 | return typeof $scope.onItemPlaced === "function" ? $scope.onItemPlaced(state) : void 0;
149 | case 'item-removed':
150 | return typeof $scope.onItemRemoved === "function" ? $scope.onItemRemoved(state) : void 0;
151 | case 'drag-leave':
152 | return typeof $scope.onDragLeave === "function" ? $scope.onDragLeave(state) : void 0;
153 | case 'drag-enter':
154 | return typeof $scope.onDragEnter === "function" ? $scope.onDragEnter(state) : void 0;
155 | }
156 | };
157 | }
158 | ],
159 | link: function(scope, element, attrs, ngDragAndDrop) {
160 | var bindEvents, moveEvents, onMove, onRelease, releaseEvents, unbindEvents;
161 | moveEvents = "touchmove mousemove";
162 | releaseEvents = "touchend mouseup";
163 | ngDragAndDrop.setDragAndDropElement(element);
164 | bindEvents = function() {
165 | $document.on(moveEvents, onMove);
166 | $document.on(releaseEvents, onRelease);
167 | ngDragAndDrop.on("drag-start", function() {
168 | return element.addClass("dragging");
169 | });
170 | return ngDragAndDrop.on("drag-end", function() {
171 | return element.removeClass("dragging");
172 | });
173 | };
174 | unbindEvents = function() {
175 | $document.off(moveEvents, onMove);
176 | return $document.off(releaseEvents, onRelease);
177 | };
178 | onRelease = function(e) {
179 | var draggable, dropSpot;
180 | draggable = ngDragAndDrop.getCurrentDraggable();
181 | dropSpot = ngDragAndDrop.getCurrentDroppable();
182 | if (draggable) {
183 | element.addClass("drag-return");
184 | setTimeout(function() {
185 | return element.removeClass("drag-return");
186 | }, 500);
187 | ngDragAndDrop.fireCallback('drag-end', e);
188 | draggable.deactivate();
189 | if (dropSpot && !dropSpot.isFull) {
190 | ngDragAndDrop.fireCallback('item-assigned', e);
191 | draggable.assignTo(dropSpot);
192 | dropSpot.itemDropped(draggable);
193 | } else if (dropSpot && dropSpot.isFull && scope.enableSwap) {
194 | dropSpot.items[0].returnToStartPosition();
195 | dropSpot.items[0].removeFrom(dropSpot);
196 | ngDragAndDrop.fireCallback('item-assigned', e);
197 | draggable.assignTo(dropSpot);
198 | dropSpot.itemDropped(draggable);
199 | ngDragAndDrop.fireCallback('item-removed', e);
200 | } else {
201 | draggable.isAssigned = false;
202 | if (scope.fixedPositions) {
203 | draggable.returnToStartPosition();
204 | }
205 | }
206 | if (dropSpot) {
207 | dropSpot.deactivate();
208 | }
209 | return ngDragAndDrop.setCurrentDraggable(null);
210 | }
211 | };
212 | onMove = function(e) {
213 | var draggable;
214 | draggable = ngDragAndDrop.getCurrentDraggable();
215 | if (draggable) {
216 | ngDragAndDrop.fireCallback('drag', e);
217 | if (e.touches && e.touches.length === 1) {
218 | draggable.updateOffset(e.touches[0].clientX ? e.touches[0].clientX : e.touches[0].pageX,
219 | e.touches[0].clientY ? e.touches[0].clientY : e.touches[0].pageY);
220 | } else {
221 | draggable.updateOffset(e.clientX, e.clientY);
222 | }
223 | return ngDragAndDrop.checkForIntersection();
224 | }
225 | };
226 | bindEvents();
227 | return ngDragAndDrop.trigger("ready");
228 | }
229 | };
230 | }
231 | ]);
232 |
233 | module.directive('dragItem', [
234 | '$window', '$document', '$compile', function($window, $document, $compile) {
235 | return {
236 | restrict: 'EA',
237 | require: '^dragAndDrop',
238 | scope: {
239 | x: "@",
240 | y: "@",
241 | dropTo: "@",
242 | dragId: "@",
243 | dragEnabled: "=",
244 | dragData: "=",
245 | clone: "=",
246 | lockHorizontal: "=",
247 | lockVertical: "="
248 | },
249 | link: function(scope, element, attrs, ngDragAndDrop) {
250 | var bindEvents, cloneEl, eventOffset, height, init, onPress, pressEvents, setClonePosition, startPosition, transformEl, unbindEvents, updateDimensions, w, width;
251 | cloneEl = width = height = startPosition = transformEl = eventOffset = pressEvents = w = null;
252 | updateDimensions = function() {
253 | scope.left = scope.x + element[0].offsetLeft;
254 | scope.right = scope.left + width;
255 | scope.top = scope.y + element[0].offsetTop;
256 | scope.bottom = scope.top + height;
257 | scope.midPoint = [scope.left + width / 2, scope.top + height / 2];
258 | if (scope.lockVertical) {
259 | scope.percent = 100 * (scope.left + element[0].clientWidth / 2) / element.parent()[0].clientWidth;
260 | return scope.percent = Math.min(100, Math.max(0, scope.percent));
261 | }
262 | };
263 | setClonePosition = function() {
264 | var elemRect, leftOffset, topOffset;
265 | elemRect = element[0].getBoundingClientRect();
266 | leftOffset = elemRect.left + eventOffset[0];
267 | topOffset = elemRect.top + eventOffset[1];
268 | return scope.updateOffset(leftOffset, topOffset);
269 | };
270 | scope.setPercentPostion = function(xPercent, yPercent) {
271 | var newX, newY;
272 | newY = (element.parent()[0].clientHeight * (yPercent / 100)) - element[0].clientHeight / 2;
273 | newX = (element.parent()[0].clientWidth * (xPercent / 100)) - element[0].clientWidth / 2;
274 | return scope.setPosition(newX, newY);
275 | };
276 | scope.setPosition = function(x, y) {
277 | scope.x = scope.lockHorizontal ? 0 : x;
278 | scope.y = scope.lockVertical ? 0 : y;
279 | updateDimensions();
280 | return transformEl.css({
281 | "transform": "translate(" + scope.x + "px, " + scope.y + "px)",
282 | "-webkit-transform": "translate(" + scope.x + "px, " + scope.y + "px)",
283 | "-ms-transform": "translate(" + scope.x + "px, " + scope.y + "px)"
284 | });
285 | };
286 | scope.updateOffset = function(x, y) {
287 | if (scope.clone) {
288 | return scope.setPosition(x - (eventOffset[0] + element[0].offsetLeft), y - (eventOffset[1] + element[0].offsetTop));
289 | } else {
290 | return scope.setPosition(x - (eventOffset[0] + element[0].offsetLeft), y - (eventOffset[1] + element[0].offsetTop));
291 | }
292 | };
293 | scope.returnToStartPosition = function() {
294 | return scope.setPosition(startPosition[0], startPosition[1]);
295 | };
296 | scope.assignTo = function(dropSpot) {
297 | scope.dropSpots.push(dropSpot);
298 | scope.isAssigned = true;
299 | if (dropSpot.dropId) {
300 | return element.addClass("in-" + dropSpot.dropId);
301 | }
302 | };
303 | scope.removeFrom = function(dropSpot) {
304 | var index;
305 | index = scope.dropSpots.indexOf(dropSpot);
306 | if (index > -1) {
307 | if (dropSpot.dropId) {
308 | element.removeClass("in-" + dropSpot.dropId);
309 | }
310 | scope.dropSpots.splice(index, 1);
311 | if (scope.dropSpots.length < 1) {
312 | scope.isAssigned = false;
313 | }
314 | return dropSpot.removeItem(scope);
315 | }
316 | };
317 | scope.addClass = function(className) {
318 | return element.addClass(className);
319 | };
320 | scope.removeClass = function(className) {
321 | return element.removeClass(className);
322 | };
323 | scope.toggleClass = function(className) {
324 | if (element.hasClass(className)) {
325 | return element.removeClass(className);
326 | } else {
327 | return element.addClass(className);
328 | }
329 | };
330 | scope.activate = function() {
331 | element.addClass("drag-active");
332 | return scope.isDragging = true;
333 | };
334 | scope.deactivate = function() {
335 | eventOffset = [0, 0];
336 | if (scope.clone) {
337 | cloneEl.removeClass("clone-active");
338 | }
339 | element.removeClass("drag-active");
340 | return scope.isDragging = false;
341 | };
342 | bindEvents = function() {
343 | element.on(pressEvents, onPress);
344 | return w.bind("resize", updateDimensions);
345 | };
346 | unbindEvents = function() {
347 | element.off(pressEvents, onPress);
348 | return w.unbind("resize", updateDimensions);
349 | };
350 | onPress = function(e) {
351 | var dropSpot, elemRect, i, len, ref, spot;
352 | if (!scope.dragEnabled) {
353 | return;
354 | }
355 | if (e.touches && e.touches.length === 1) {
356 | eventOffset = [e.touches[0].clientX - scope.left, e.touches[0].clientY - scope.top];
357 | } else {
358 | elemRect = element[0].getBoundingClientRect();
359 | eventOffset = [e.clientX - elemRect.left, e.clientY - elemRect.top];
360 | }
361 | if (scope.clone) {
362 | scope.returnToStartPosition();
363 | cloneEl.addClass("clone-active");
364 | setClonePosition();
365 | }
366 | ngDragAndDrop.setCurrentDraggable(scope);
367 | scope.activate();
368 | scope.isAssigned = false;
369 | ngDragAndDrop.checkForIntersection();
370 | dropSpot = ngDragAndDrop.getCurrentDroppable();
371 | ref = scope.dropSpots;
372 | for (i = 0, len = ref.length; i < len; i++) {
373 | spot = ref[i];
374 | scope.removeFrom(spot);
375 | ngDragAndDrop.fireCallback('item-removed', e);
376 | }
377 | return e.preventDefault();
378 | };
379 | init = function() {
380 | var testing;
381 | if (scope.dragId) {
382 | element.addClass(scope.dragId);
383 | }
384 | eventOffset = [0, 0];
385 | width = element[0].offsetWidth;
386 | height = element[0].offsetHeight;
387 | scope.dropSpots = [];
388 | scope.isAssigned = false;
389 | if (scope.x == null) {
390 | scope.x = 0;
391 | }
392 | if (scope.y == null) {
393 | scope.y = 0;
394 | }
395 | startPosition = [scope.x, scope.y];
396 | pressEvents = "touchstart mousedown";
397 | w = angular.element($window);
398 | updateDimensions();
399 | ngDragAndDrop.addDraggable(scope);
400 | bindEvents();
401 | if (scope.dragData) {
402 | angular.extend(scope, scope.dragData);
403 | }
404 | if (scope.clone) {
405 | scope[scope.dragData.key] = scope.dragData.value;
406 | testing = $compile(angular.element("
" + element.html() + "
"))(scope);
407 | cloneEl = testing;
408 | cloneEl.addClass("clone");
409 | cloneEl.addClass(element.attr("class"));
410 | angular.element(ngDragAndDrop.getDragAndDropElement()).append(cloneEl);
411 | transformEl = cloneEl;
412 | } else {
413 | transformEl = element;
414 | }
415 | scope.returnToStartPosition();
416 | scope.$emit('drag-ready', scope);
417 | return scope.$on('$destroy', function() {
418 | return unbindEvents();
419 | });
420 | };
421 | return ngDragAndDrop.on("ready", init);
422 | }
423 | };
424 | }
425 | ]);
426 |
427 | module.directive('dropSpot', [
428 | '$window', function($window) {
429 | return {
430 | restrict: 'AE',
431 | require: '^dragAndDrop',
432 | transclude: true,
433 | template: "",
434 | scope: {
435 | dropId: "@",
436 | maxItems: "="
437 | },
438 | link: function(scope, element, attrs, ngDragAndDrop) {
439 | var addItem, bindEvents, getDroppedPosition, handleResize, unbindEvents, updateDimensions, w;
440 | updateDimensions = function() {
441 | scope.left = element[0].offsetLeft;
442 | scope.top = element[0].offsetTop;
443 | scope.right = scope.left + element[0].offsetWidth;
444 | return scope.bottom = scope.top + element[0].offsetHeight;
445 | };
446 | getDroppedPosition = function(item) {
447 | var dropSize, itemSize, xPos, yPos;
448 | dropSize = [scope.right - scope.left, scope.bottom - scope.top];
449 | itemSize = [item.right - item.left, item.bottom - item.top];
450 | switch (item.dropTo) {
451 | case "top":
452 | xPos = scope.left + (dropSize[0] - itemSize[0]) / 2;
453 | yPos = scope.top;
454 | break;
455 | case "bottom":
456 | xPos = scope.left + (dropSize[0] - itemSize[0]) / 2;
457 | yPos = scope.top + (dropSize[1] - itemSize[1]);
458 | break;
459 | case "left":
460 | xPos = scope.left;
461 | yPos = scope.top + (dropSize[1] - itemSize[1]) / 2;
462 | break;
463 | case "right":
464 | xPos = scope.left + (dropSize[0] - itemSize[0]);
465 | yPos = scope.top + (dropSize[1] - itemSize[1]) / 2;
466 | break;
467 | case "top left":
468 | xPos = scope.left;
469 | yPos = scope.top;
470 | break;
471 | case "bottom right":
472 | xPos = scope.left + (dropSize[0] - itemSize[0]);
473 | yPos = scope.top + (dropSize[1] - itemSize[1]);
474 | break;
475 | case "bottom left":
476 | xPos = scope.left;
477 | yPos = scope.top + (dropSize[1] - itemSize[1]);
478 | break;
479 | case "top right":
480 | xPos = scope.left + (dropSize[0] - itemSize[0]);
481 | yPos = scope.top;
482 | break;
483 | case "center":
484 | xPos = scope.left + (dropSize[0] - itemSize[0]) / 2;
485 | yPos = scope.top + (dropSize[1] - itemSize[1]) / 2;
486 | break;
487 | default:
488 | if (item.dropOffset) {
489 | xPos = scope.left + item.dropOffset[0];
490 | yPos = scope.top + item.dropOffset[1];
491 | }
492 | }
493 | return [xPos, yPos];
494 | };
495 | scope.itemDropped = function(item) {
496 | var added, newPos;
497 | added = addItem(item);
498 | if (added) {
499 | if (item.dropTo) {
500 | newPos = getDroppedPosition(item);
501 | return item.updateOffset(newPos[0], newPos[1]);
502 | } else {
503 | return item.dropOffset = [item.left - scope.left, item.top - scope.top];
504 | }
505 | } else {
506 | if (scope.fixedPositions) {
507 | return item.returnToStartPosition();
508 | }
509 | }
510 | };
511 | addItem = function(item) {
512 | if (!scope.isFull) {
513 | scope.items.push(item);
514 | if (scope.items.length >= scope.maxItems) {
515 | scope.isFull = true;
516 | }
517 | return item;
518 | }
519 | return false;
520 | };
521 | scope.removeItem = function(item) {
522 | var index;
523 | index = scope.items.indexOf(item);
524 | if (index > -1) {
525 | scope.items.splice(index, 1);
526 | if (scope.items.length < scope.maxItems) {
527 | return scope.isFull = false;
528 | }
529 | }
530 | };
531 | scope.activate = function() {
532 | scope.isActive = true;
533 | return element.addClass("drop-hovering");
534 | };
535 | scope.deactivate = function() {
536 | scope.isActive = false;
537 | ngDragAndDrop.setCurrentDroppable(null);
538 | return element.removeClass("drop-hovering");
539 | };
540 | handleResize = function() {
541 | var i, item, len, newPos, ref, results;
542 | updateDimensions();
543 | ref = scope.items;
544 | results = [];
545 | for (i = 0, len = ref.length; i < len; i++) {
546 | item = ref[i];
547 | newPos = getDroppedPosition(item);
548 | results.push(item.updateOffset(newPos[0], newPos[1]));
549 | }
550 | return results;
551 | };
552 | bindEvents = function() {
553 | return w.bind("resize", handleResize);
554 | };
555 | unbindEvents = function() {
556 | return w.unbind("resize", handleResize);
557 | };
558 | if (scope.dropId) {
559 | element.addClass(scope.dropId);
560 | }
561 | w = angular.element($window);
562 | bindEvents();
563 | scope.$on('$destroy', function() {
564 | return unbindEvents();
565 | });
566 | updateDimensions();
567 | scope.isActive = false;
568 | scope.items = [];
569 | return ngDragAndDrop.addDroppable(scope);
570 | }
571 | };
572 | }
573 | ]);
574 |
--------------------------------------------------------------------------------
/build/js/angular-drag-and-drop.min.js:
--------------------------------------------------------------------------------
1 | var module;module=angular.module("laneolson.ui.dragdrop",[]),module.directive("dragAndDrop",["$document",function(t){return{restrict:"AE",scope:{onItemPlaced:"&",onItemRemoved:"&",onDrag:"&",onDragStart:"&",onDragEnd:"&",onDragEnter:"&",onDragLeave:"&",enableSwap:"=",fixedPositions:"="},require:"dragAndDrop",controller:["$q","$scope",function(t,e){var r,n,o,a,i,s,u,l;e.draggables=n=[],e.droppables=o=[],e.isDragging=!1,e.currentDraggable=null,r=null,a=null,l=!1,i=[],s=function(t,e){var r,n;return e.left<(r=t[0])&&rt.right||e.rightt.bottom||e.bottomr;r++)e=i[r],e.name===t?o.push(e.cb()):o.push(void 0);return o},this.isReady=function(){return l},this.setDragAndDropElement=function(t){return a=t},this.getDragAndDropElement=function(){return a},this.checkForIntersection=function(){var t,r,n,a;for(a=[],r=0,n=o.length;n>r;r++)t=o[r],s(e.currentDraggable.midPoint,t)?t.isActive?a.push(void 0):(this.setCurrentDroppable(t),t.activate(),a.push(this.fireCallback("drag-enter"))):t.isActive?(this.setCurrentDroppable(null),t.deactivate(),a.push(this.fireCallback("drag-leave"))):a.push(void 0);return a},this.setCurrentDraggable=function(t){return e.currentDraggable=t,t&&this.fireCallback("drag-start"),e.$evalAsync(function(){return e.currentDraggable=t,t?e.isDragging=!0:e.isDragging=!1})},this.getCurrentDraggable=function(){return e.currentDraggable},this.setCurrentDroppable=function(t){return r=t},this.getCurrentDroppable=function(){return r},this.addDroppable=function(t){return o.push(t)},this.addDraggable=function(t){return n.push(t)},this.fireCallback=function(t,r){var n;switch(n={draggable:this.getCurrentDraggable(),droppable:this.getCurrentDroppable(),dragEvent:r},t){case"drag-end":return"function"==typeof e.onDragEnd&&e.onDragEnd(n),this.trigger("drag-end");case"drag-start":return"function"==typeof e.onDragStart&&e.onDragStart(n),this.trigger("drag-start");case"drag":return"function"==typeof e.onDrag?e.onDrag(n):void 0;case"item-assigned":return"function"==typeof e.onItemPlaced?e.onItemPlaced(n):void 0;case"item-removed":return"function"==typeof e.onItemRemoved?e.onItemRemoved(n):void 0;case"drag-leave":return"function"==typeof e.onDragLeave?e.onDragLeave(n):void 0;case"drag-enter":return"function"==typeof e.onDragEnter?e.onDragEnter(n):void 0}}}],link:function(e,r,n,o){var a,i,s,u,l,d;return i="touchmove mousemove",l="touchend mouseup",o.setDragAndDropElement(r),a=function(){return t.on(i,s),t.on(l,u),o.on("drag-start",function(){return r.addClass("dragging")}),o.on("drag-end",function(){return r.removeClass("dragging")})},d=function(){return t.off(i,s),t.off(l,u)},u=function(t){var n,a;return n=o.getCurrentDraggable(),a=o.getCurrentDroppable(),n?(r.addClass("drag-return"),setTimeout(function(){return r.removeClass("drag-return")},500),o.fireCallback("drag-end",t),n.deactivate(),a&&!a.isFull?(o.fireCallback("item-assigned",t),n.assignTo(a),a.itemDropped(n)):a&&a.isFull&&e.enableSwap?(a.items[0].returnToStartPosition(),a.items[0].removeFrom(a),o.fireCallback("item-assigned",t),n.assignTo(a),a.itemDropped(n),o.fireCallback("item-removed",t)):(n.isAssigned=!1,e.fixedPositions&&n.returnToStartPosition()),a&&a.deactivate(),o.setCurrentDraggable(null)):void 0},s=function(t){var e;return e=o.getCurrentDraggable(),e?(o.fireCallback("drag",t),t.touches&&1===t.touches.length?e.updateOffset(t.touches[0].clientX,t.touches[0].clientY):e.updateOffset(t.clientX,t.clientY),o.checkForIntersection()):void 0},a(),o.trigger("ready")}}}]),module.directive("dragItem",["$window","$document","$compile",function(t,e,r){return{restrict:"EA",require:"^dragAndDrop",scope:{x:"@",y:"@",dropTo:"@",dragId:"@",dragEnabled:"=",dragData:"=",clone:"=",lockHorizontal:"=",lockVertical:"="},link:function(e,n,o,a){var i,s,u,l,d,c,f,g,p,m,v,h,b,D;return s=D=l=p=m=u=f=b=null,h=function(){return e.left=e.x+n[0].offsetLeft,e.right=e.left+D,e.top=e.y+n[0].offsetTop,e.bottom=e.top+l,e.midPoint=[e.left+D/2,e.top+l/2],e.lockVertical?(e.percent=100*(e.left+n[0].clientWidth/2)/n.parent()[0].clientWidth,e.percent=Math.min(100,Math.max(0,e.percent))):void 0},g=function(){var t,r,o;return t=n[0].getBoundingClientRect(),r=t.left+u[0],o=t.top+u[1],e.updateOffset(r,o)},e.setPercentPostion=function(t,r){var o,a;return a=n.parent()[0].clientHeight*(r/100)-n[0].clientHeight/2,o=n.parent()[0].clientWidth*(t/100)-n[0].clientWidth/2,e.setPosition(o,a)},e.setPosition=function(t,r){return e.x=e.lockHorizontal?0:t,e.y=e.lockVertical?0:r,h(),m.css({transform:"translate("+e.x+"px, "+e.y+"px)","-webkit-transform":"translate("+e.x+"px, "+e.y+"px)","-ms-transform":"translate("+e.x+"px, "+e.y+"px)"})},e.updateOffset=function(t,r){return e.clone?e.setPosition(t-(u[0]+n[0].offsetLeft),r-(u[1]+n[0].offsetTop)):e.setPosition(t-(u[0]+n[0].offsetLeft),r-(u[1]+n[0].offsetTop))},e.returnToStartPosition=function(){return e.setPosition(p[0],p[1])},e.assignTo=function(t){return e.dropSpots.push(t),e.isAssigned=!0,t.dropId?n.addClass("in-"+t.dropId):void 0},e.removeFrom=function(t){var r;return r=e.dropSpots.indexOf(t),r>-1?(t.dropId&&n.removeClass("in-"+t.dropId),e.dropSpots.splice(r,1),e.dropSpots.length<1&&(e.isAssigned=!1),t.removeItem(e)):void 0},e.addClass=function(t){return n.addClass(t)},e.removeClass=function(t){return n.removeClass(t)},e.toggleClass=function(t){return n.hasClass(t)?n.removeClass(t):n.addClass(t)},e.activate=function(){return n.addClass("drag-active"),e.isDragging=!0},e.deactivate=function(){return u=[0,0],e.clone&&s.removeClass("clone-active"),n.removeClass("drag-active"),e.isDragging=!1},i=function(){return n.on(f,c),b.bind("resize",h)},v=function(){return n.off(f,c),b.unbind("resize",h)},c=function(t){var r,o,i,l,d,c;if(e.dragEnabled){for(t.touches&&1===t.touches.length?u=[t.touches[0].clientX-e.left,t.touches[0].clientY-e.top]:(o=n[0].getBoundingClientRect(),u=[t.clientX-o.left,t.clientY-o.top]),e.clone&&(e.returnToStartPosition(),s.addClass("clone-active"),g()),a.setCurrentDraggable(e),e.activate(),e.isAssigned=!1,a.checkForIntersection(),r=a.getCurrentDroppable(),d=e.dropSpots,i=0,l=d.length;l>i;i++)c=d[i],e.removeFrom(c),a.fireCallback("item-removed",t);return t.preventDefault()}},d=function(){var o;return e.dragId&&n.addClass(e.dragId),u=[0,0],D=n[0].offsetWidth,l=n[0].offsetHeight,e.dropSpots=[],e.isAssigned=!1,null==e.x&&(e.x=0),null==e.y&&(e.y=0),p=[e.x,e.y],f="touchstart mousedown",b=angular.element(t),h(),a.addDraggable(e),i(),e.dragData&&angular.extend(e,e.dragData),e.clone?(e[e.dragData.key]=e.dragData.value,o=r(angular.element(""+n.html()+"
"))(e),s=o,s.addClass("clone"),s.addClass(n.attr("class")),angular.element(a.getDragAndDropElement()).append(s),m=s):m=n,e.returnToStartPosition(),e.$emit("drag-ready",e),e.$on("$destroy",function(){return v()})},a.on("ready",d)}}}]),module.directive("dropSpot",["$window",function(t){return{restrict:"AE",require:"^dragAndDrop",transclude:!0,template:"",scope:{dropId:"@",maxItems:"="},link:function(e,r,n,o){var a,i,s,u,l,d,c;return d=function(){return e.left=r[0].offsetLeft,e.top=r[0].offsetTop,e.right=e.left+r[0].offsetWidth,e.bottom=e.top+r[0].offsetHeight},s=function(t){var r,n,o,a;switch(r=[e.right-e.left,e.bottom-e.top],n=[t.right-t.left,t.bottom-t.top],t.dropTo){case"top":o=e.left+(r[0]-n[0])/2,a=e.top;break;case"bottom":o=e.left+(r[0]-n[0])/2,a=e.top+(r[1]-n[1]);break;case"left":o=e.left,a=e.top+(r[1]-n[1])/2;break;case"right":o=e.left+(r[0]-n[0]),a=e.top+(r[1]-n[1])/2;break;case"top left":o=e.left,a=e.top;break;case"bottom right":o=e.left+(r[0]-n[0]),a=e.top+(r[1]-n[1]);break;case"bottom left":o=e.left,a=e.top+(r[1]-n[1]);break;case"top right":o=e.left+(r[0]-n[0]),a=e.top;break;case"center":o=e.left+(r[0]-n[0])/2,a=e.top+(r[1]-n[1])/2;break;default:t.dropOffset&&(o=e.left+t.dropOffset[0],a=e.top+t.dropOffset[1])}return[o,a]},e.itemDropped=function(t){var r,n;return r=a(t),r?t.dropTo?(n=s(t),t.updateOffset(n[0],n[1])):t.dropOffset=[t.left-e.left,t.top-e.top]:e.fixedPositions?t.returnToStartPosition():void 0},a=function(t){return e.isFull?!1:(e.items.push(t),e.items.length>=e.maxItems&&(e.isFull=!0),t)},e.removeItem=function(t){var r;return r=e.items.indexOf(t),r>-1&&(e.items.splice(r,1),e.items.lengtht;t++)r=a[t],o=s(r),i.push(r.updateOffset(o[0],o[1]));return i},i=function(){return c.bind("resize",u)},l=function(){return c.unbind("resize",u)},e.dropId&&r.addClass(e.dropId),c=angular.element(t),i(),e.$on("$destroy",function(){return l()}),d(),e.isActive=!1,e.items=[],o.addDroppable(e)}}}]);
--------------------------------------------------------------------------------
/examples/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
14 |
115 |
116 |
117 |
120 |
121 |