├── README.md
├── bower.json
├── dragon-drop.js
└── example.html
/README.md:
--------------------------------------------------------------------------------
1 | # angular-dragon-drop
2 | "Drag and drop" directives for AngularJS. Work in progress.
3 |
4 | ## Install
5 |
6 | ```shell
7 | bower install angular-dragon-drop
8 | ```
9 |
10 | ## Usage
11 | 1. Include the `dragon-drop.js` script provided by this component into your app.
12 | 2. Add `btford.dragon-drop` as a module dependency to your app.
13 |
14 | Repeats a template inside the dragon over a list.
15 | ```html
16 |
17 | {{item.name}}
18 |
19 |
20 | {{item.name}}
21 |
22 | ```
23 | You can drag from one dragon onto another, and the models will be updated accordingly.
24 |
25 | It also works on objects:
26 | ```html
27 |
28 | {{key}}: {{value}}
29 |
30 |
31 | {{key}}: {{value}}
32 |
33 | ```
34 |
35 |
36 | ## Config
37 | This is not a kitchen sink every-option-you-can-think-of module.
38 | This is a starting point.
39 | Configure by forking and editing the code according to your needs.
40 | Send a PR if you think your additions are widely useful. :)
41 |
42 | ### btf-double-dragon
43 | Instead of removing values from the array this dragon is bound to, the values are duplicated.
44 | Add the `btf-double-dragon` attribute to an element with the `btf-dragon` attribute to get the behavior.
45 |
46 | Example:
47 | ```html
48 | These get copied
49 |
50 | {{item.name}}
51 |
52 | These get moved
53 |
54 | {{item.name}}
55 |
56 | ```
57 |
58 | ### btf-dragon-accepts
59 | Makes the dragon only accepts items that pass the truth test function given by this argument.
60 | Add the `btf-dragon-accepts` attribute to an element to get the behavior.
61 |
62 | Example:
63 | ```html
64 | You can only put shiny objects here h2>
65 |
66 | {{item.name}}
67 |
68 | This takes anything
69 |
70 | {{item.name}}
71 |
72 | ```
73 |
74 | ```javascript
75 | // in a Ctrl...
76 | $scope.shinyThings = function (item) {
77 | return !!item.shiny;
78 | };
79 | ```
80 |
81 | ## Example
82 | See [`example.html`](http://htmlpreview.github.io/?https://github.com/btford/angular-dragon-drop/blob/master/example.html).
83 |
84 | ## License
85 | MIT
86 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular-dragon-drop",
3 | "version": "0.3.1",
4 | "main": "dragon-drop.js",
5 | "dependencies": {
6 | "angular": "~1.0.5"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/dragon-drop.js:
--------------------------------------------------------------------------------
1 | /*
2 | * angular-dragon-drop v0.3.1
3 | * (c) 2013 Brian Ford http://briantford.com
4 | * License: MIT
5 | */
6 |
7 | 'use strict';
8 |
9 | angular.module('btford.dragon-drop', []).
10 | directive('btfDragon', function ($document, $compile, $rootScope) {
11 | /*
12 | ^ ^
13 | |\ \ / /|
14 | / \ |\__ __/| / \
15 | / /\ \ \ _ \/ _ / / \
16 | / / /\ \ {*}\/{*} / / \ \
17 | | | | \ \( (00) ) / // |\ \
18 | | | | |\ \(V""V)\ / / | || \|
19 | | | | | \ |^--^| \ / / || || ||
20 | / / / | |( WWWW__ \/ /| || || ||
21 | | | | | | | \______\ / / || || ||
22 | | | | / | | )|______\ ) | / | || ||
23 | / / / / / /______/ /| \ \ || ||
24 | / / / / / /\_____/ |/ /__\ \ \ \ \
25 | | | | / / /\______/ \ \__| \ \ \
26 | | | | | | |\______ __ \_ \__|_| \
27 | | | ,___ /\______ _ _ \_ \ |
28 | | |/ /\_____ / \ \__ \ | /\
29 | |/ | |\______ | | \___ \ |__/ \
30 | v | |\______ | | \___/ |
31 | | |\______ | | __/
32 | \ \________\_ _\ ____/
33 | __/ /\_____ __/ / )\_, _____/
34 | / ___/ \uuuu/ ___/___) \______/
35 | VVV V VVV V
36 | */
37 | // this ASCII dragon is really important, do not remove
38 |
39 | var dragValue,
40 | dragKey,
41 | dragOrigin,
42 | dragDuplicate = false,
43 | floaty,
44 | offsetX,
45 | offsetY;
46 |
47 | var drag = function (ev) {
48 | var x = ev.clientX - offsetX,
49 | y = ev.clientY - offsetY;
50 |
51 | floaty.css('left', x + 'px');
52 | floaty.css('top', y + 'px');
53 | };
54 |
55 | var remove = function (collection, index) {
56 | if (collection instanceof Array) {
57 | return collection.splice(index, 1);
58 | } else {
59 | var temp = collection[index];
60 | delete collection[index];
61 | return temp;
62 | }
63 | };
64 |
65 | var add = function (collection, item, key) {
66 | if (collection instanceof Array) {
67 | collection.push(item);
68 | } else {
69 | collection[key] = item;
70 | }
71 | };
72 |
73 | var documentBody = angular.element($document[0].body);
74 |
75 | var disableSelect = function () {
76 | documentBody.css({
77 | '-moz-user-select': '-moz-none',
78 | '-khtml-user-select': 'none',
79 | '-webkit-user-select': 'none',
80 | '-ms-user-select': 'none',
81 | 'user-select': 'none'
82 | });
83 | };
84 |
85 | var enableSelect = function () {
86 | documentBody.css({
87 | '-moz-user-select': '',
88 | '-khtml-user-select': '',
89 | '-webkit-user-select': '',
90 | '-ms-user-select': '',
91 | 'user-select': ''
92 | });
93 | };
94 |
95 | var killFloaty = function () {
96 | if (floaty) {
97 | $document.unbind('mousemove', drag);
98 | floaty.remove();
99 | floaty = null;
100 | }
101 | };
102 |
103 | var getElementOffset = function (elt) {
104 |
105 | var box = elt.getBoundingClientRect();
106 | var body = $document[0].body;
107 |
108 | var xPosition = box.left + body.scrollLeft;
109 | var yPosition = box.top + body.scrollTop;
110 |
111 | return {
112 | left: xPosition,
113 | top: yPosition
114 | };
115 | };
116 |
117 | // Get the element at position (`x`, `y`) behind the given element
118 | var getElementBehindPoint = function (behind, x, y) {
119 | var originalDisplay = behind.css('display');
120 | behind.css('display', 'none');
121 |
122 | var element = angular.element($document[0].elementFromPoint(x, y));
123 |
124 | behind.css('display', originalDisplay);
125 |
126 | return element;
127 | };
128 |
129 | $document.bind('mouseup', function (ev) {
130 | if (!dragValue) {
131 | return;
132 | }
133 |
134 | var dropArea = getElementBehindPoint(floaty, ev.clientX, ev.clientY);
135 |
136 | var accepts = function () {
137 | return dropArea.attr('btf-dragon') &&
138 | ( !dropArea.attr('btf-dragon-accepts') ||
139 | dropArea.scope().$eval(dropArea.attr('btf-dragon-accepts'))(dragValue) );
140 | };
141 |
142 | while (dropArea.length > 0 && !accepts()) {
143 | dropArea = dropArea.parent();
144 | }
145 |
146 | if (dropArea.length > 0) {
147 | var expression = dropArea.attr('btf-dragon');
148 | var targetScope = dropArea.scope();
149 | var match = expression.match(/^\s*(.+)\s+in\s+(.*?)\s*$/);
150 |
151 | var targetList = targetScope.$eval(match[2]);
152 | targetScope.$apply(function () {
153 | add(targetList, dragValue, dragKey);
154 | });
155 | } else if (!dragDuplicate) {
156 | // no dropArea here
157 | // put item back to origin
158 | $rootScope.$apply(function () {
159 | add(dragOrigin, dragValue, dragKey);
160 | });
161 | }
162 |
163 | dragValue = dragOrigin = null;
164 | killFloaty();
165 | });
166 |
167 | return {
168 | restrict: 'A',
169 |
170 | compile: function (container, attr) {
171 |
172 | // get the `thing in things` expression
173 | var expression = attr.btfDragon;
174 | var match = expression.match(/^\s*(.+)\s+in\s+(.*?)\s*$/);
175 | if (!match) {
176 | throw Error("Expected btfDragon in form of '_item_ in _collection_' but got '" +
177 | expression + "'.");
178 | }
179 | var lhs = match[1];
180 | var rhs = match[2];
181 |
182 | match = lhs.match(/^(?:([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\))$/);
183 |
184 | var valueIdentifier = match[3] || match[1];
185 | var keyIdentifier = match[2];
186 |
187 | // pull out the template to re-use.
188 | // Improvised ng-transclude.
189 | var template = container.html();
190 |
191 | // wrap text nodes
192 | try {
193 | template = angular.element(template.trim());
194 | if (template.length === 0) {
195 | throw new Error('');
196 | }
197 | }
198 | catch (e) {
199 | template = angular.element('' + template + ' ');
200 | }
201 | var child = template.clone();
202 | child.attr('ng-repeat', expression);
203 |
204 | container.html('');
205 | container.append(child);
206 |
207 | var duplicate = container.attr('btf-double-dragon') !== undefined;
208 |
209 | return function (scope, elt, attr) {
210 |
211 | var accepts = scope.$eval(attr.btfDragonAccepts);
212 |
213 | if (accepts !== undefined && typeof accepts !== 'function') {
214 | throw Error('Expected btfDragonAccepts to be a function.');
215 | }
216 |
217 | var spawnFloaty = function () {
218 | scope.$apply(function () {
219 | floaty = template.clone();
220 | floaty.css('position', 'fixed');
221 |
222 | floaty.css('margin', '0px');
223 | floaty.css('z-index', '99999');
224 |
225 | var floatyScope = scope.$new();
226 | floatyScope[valueIdentifier] = dragValue;
227 | if (keyIdentifier) {
228 | floatyScope[keyIdentifier] = dragKey;
229 | }
230 | $compile(floaty)(floatyScope);
231 | documentBody.append(floaty);
232 | $document.bind('mousemove', drag);
233 | disableSelect();
234 | });
235 | };
236 |
237 | elt.bind('mousedown', function (ev) {
238 | if (dragValue) {
239 | return;
240 | }
241 |
242 | // find the right parent
243 | var originElement = angular.element(ev.target);
244 | var originScope = originElement.scope();
245 |
246 | while (originScope[valueIdentifier] === undefined) {
247 | originScope = originScope.$parent;
248 | if (!originScope) {
249 | return;
250 | }
251 | }
252 |
253 | dragValue = originScope[valueIdentifier];
254 | dragKey = originScope[keyIdentifier];
255 | if (!dragValue) {
256 | return;
257 | }
258 |
259 | // get offset inside element to drag
260 | var offset = getElementOffset(ev.target);
261 |
262 | dragOrigin = scope.$eval(rhs);
263 | if (duplicate) {
264 | dragValue = angular.copy(dragValue);
265 | } else {
266 | scope.$apply(function () {
267 | remove(dragOrigin, dragKey || dragOrigin.indexOf(dragValue));
268 | });
269 | }
270 | dragDuplicate = duplicate;
271 |
272 | offsetX = (ev.pageX - offset.left);
273 | offsetY = (ev.pageY - offset.top);
274 |
275 | spawnFloaty();
276 | drag(ev);
277 | });
278 | };
279 | }
280 | };
281 | });
282 |
--------------------------------------------------------------------------------
/example.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Dragon Drop for AngularJS
5 |
6 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
Dragon Drop Example
19 |
20 |
21 |
22 |
23 |
24 |
25 |
Things
26 |
{{thing}}
27 |
28 |
29 |
Other Things
30 |
{{thing}}
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
Things
39 |
{{things | json}}
40 |
41 |
42 |
Other Things
43 |
{{otherThings | json}}
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
58 |
59 |
--------------------------------------------------------------------------------