Built motion from commit 7e022ab.|2.0.15
[motion2.git] / public / bower_components / angular-native-dragdrop / draganddrop.js
1 (function(angular) {
2     'use strict';
3
4     function isDnDsSupported() {
5         return 'ondrag' in document.createElement('a');
6     }
7
8     function determineEffectAllowed(e) {
9         // Chrome doesn't set dropEffect, so we have to work it out ourselves
10         if (e.dataTransfer && e.dataTransfer.dropEffect === 'none') {
11             if (e.dataTransfer.effectAllowed === 'copy' ||
12                 e.dataTransfer.effectAllowed === 'move') {
13                 e.dataTransfer.dropEffect = e.dataTransfer.effectAllowed;
14             } else if (e.dataTransfer.effectAllowed === 'copyMove' || e.dataTransfer.effectAllowed === 'copymove') {
15                 e.dataTransfer.dropEffect = e.ctrlKey ? 'copy' : 'move';
16             }
17         }
18     }
19
20     if (!isDnDsSupported()) {
21         angular.module('ang-drag-drop', []);
22         return;
23     }
24
25     if (window.jQuery && (-1 === window.jQuery.event.props.indexOf('dataTransfer'))) {
26         window.jQuery.event.props.push('dataTransfer');
27     }
28
29     var module = angular.module('ang-drag-drop', []);
30
31     module.directive('uiDraggable', ['$parse', '$rootScope', '$dragImage', function($parse, $rootScope, $dragImage) {
32         return function(scope, element, attrs) {
33             var isDragHandleUsed = false,
34                 dragHandleClass,
35                 draggingClass = attrs.draggingClass || 'on-dragging',
36                 dragTarget;
37
38             element.attr('draggable', false);
39
40             scope.$watch(attrs.uiDraggable, function(newValue) {
41                 if (newValue) {
42                     element.attr('draggable', newValue);
43                     element.bind('dragend', dragendHandler);
44                     element.bind('dragstart', dragstartHandler);
45                 }
46                 else {
47                     element.removeAttr('draggable');
48                     element.unbind('dragend', dragendHandler);
49                     element.unbind('dragstart', dragstartHandler);
50                 }
51
52             });
53
54             if (angular.isString(attrs.dragHandleClass)) {
55                 isDragHandleUsed = true;
56                 dragHandleClass = attrs.dragHandleClass.trim() || 'drag-handle';
57
58                 element.bind('mousedown', function(e) {
59                     dragTarget = e.target;
60                 });
61             }
62
63             function dragendHandler(e) {
64                 setTimeout(function() {
65                     element.unbind('$destroy', dragendHandler);
66                 }, 0);
67                 var sendChannel = attrs.dragChannel || 'defaultchannel';
68                 $rootScope.$broadcast('ANGULAR_DRAG_END', e, sendChannel);
69
70                 determineEffectAllowed(e);
71
72                 if (e.dataTransfer && e.dataTransfer.dropEffect !== 'none') {
73                     if (attrs.onDropSuccess) {
74                         var onDropSuccessFn = $parse(attrs.onDropSuccess);
75                         scope.$evalAsync(function() {
76                             onDropSuccessFn(scope, {$event: e});
77                         });
78                     } else {
79                         if (attrs.onDropFailure) {
80                             var onDropFailureFn = $parse(attrs.onDropFailure);
81                             scope.$evalAsync(function() {
82                                 onDropFailureFn(scope, {$event: e});
83                             });
84                         }
85                     }
86                 }
87                 element.removeClass(draggingClass);
88             }
89
90             function dragstartHandler(e) {
91                 var isDragAllowed = !isDragHandleUsed || dragTarget.classList.contains(dragHandleClass);
92
93                 if (isDragAllowed) {
94                     var sendChannel = attrs.dragChannel || 'defaultchannel';
95                     var dragData = '';
96                     if (attrs.drag) {
97                         dragData = scope.$eval(attrs.drag);
98                     }
99
100                     var dragImage = attrs.dragImage || null;
101
102                     element.addClass(draggingClass);
103                     element.bind('$destroy', dragendHandler);
104
105                     //Code to make sure that the setDragImage is available. IE 10, 11, and Opera do not support setDragImage.
106                     var hasNativeDraggable = !(document.uniqueID || window.opera);
107
108                     //If there is a draggable image passed in, then set the image to be dragged.
109                     if (dragImage && hasNativeDraggable) {
110                         var dragImageFn = $parse(attrs.dragImage);
111                         scope.$apply(function() {
112                             var dragImageParameters = dragImageFn(scope, {$event: e});
113                             if (dragImageParameters) {
114                                 if (angular.isString(dragImageParameters)) {
115                                     dragImageParameters = $dragImage.generate(dragImageParameters);
116                                 }
117                                 if (dragImageParameters.image) {
118                                     var xOffset = dragImageParameters.xOffset || 0,
119                                         yOffset = dragImageParameters.yOffset || 0;
120                                     e.dataTransfer.setDragImage(dragImageParameters.image, xOffset, yOffset);
121                                 }
122                             }
123                         });
124                     }
125
126                     var transferDataObject = {data: dragData, channel: sendChannel};
127                     var transferDataText = angular.toJson(transferDataObject);
128
129                     e.dataTransfer.setData('text', transferDataText);
130                     e.dataTransfer.effectAllowed = 'copyMove';
131
132                     $rootScope.$broadcast('ANGULAR_DRAG_START', e, sendChannel, transferDataObject);
133                 }
134                 else {
135                     e.preventDefault();
136                 }
137             }
138         };
139     }
140     ]);
141
142     module.directive('uiOnDrop', ['$parse', '$rootScope', function($parse, $rootScope) {
143         return function(scope, element, attr) {
144             var dragging = 0; //Ref. http://stackoverflow.com/a/10906204
145             var dropChannel = attr.dropChannel || 'defaultchannel';
146             var dragChannel = '';
147             var dragEnterClass = attr.dragEnterClass || 'on-drag-enter';
148             var dragHoverClass = attr.dragHoverClass || 'on-drag-hover';
149             var customDragEnterEvent = $parse(attr.onDragEnter);
150             var customDragLeaveEvent = $parse(attr.onDragLeave);
151
152             function onDragOver(e) {
153                 if (e.preventDefault) {
154                     e.preventDefault(); // Necessary. Allows us to drop.
155                 }
156
157                 if (e.stopPropagation) {
158                     e.stopPropagation();
159                 }
160
161                 var uiOnDragOverFn = $parse(attr.uiOnDragOver);
162                 scope.$evalAsync(function() {
163                     uiOnDragOverFn(scope, {$event: e, $channel: dropChannel});
164                 });
165
166                 return false;
167             }
168
169             function onDragLeave(e) {
170                 if (e.preventDefault) {
171                     e.preventDefault();
172                 }
173
174                 if (e.stopPropagation) {
175                     e.stopPropagation();
176                 }
177                 dragging--;
178
179                 if (dragging === 0) {
180                     scope.$evalAsync(function() {
181                         customDragLeaveEvent(scope, {$event: e, $channel: dropChannel});
182                     });
183                     element.addClass(dragEnterClass);
184                     element.removeClass(dragHoverClass);
185                 }
186
187                 var uiOnDragLeaveFn = $parse(attr.uiOnDragLeave);
188                 scope.$evalAsync(function() {
189                     uiOnDragLeaveFn(scope, {$event: e, $channel: dropChannel});
190                 });
191             }
192
193             function onDragEnter(e) {
194                 if (e.preventDefault) {
195                     e.preventDefault();
196                 }
197
198                 if (e.stopPropagation) {
199                     e.stopPropagation();
200                 }
201
202                 if (dragging === 0) {
203                     scope.$evalAsync(function() {
204                         customDragEnterEvent(scope, {$event: e, $channel: dropChannel});
205                     });
206                     element.removeClass(dragEnterClass);
207                     element.addClass(dragHoverClass);
208                 }
209                 dragging++;
210
211                 var uiOnDragEnterFn = $parse(attr.uiOnDragEnter);
212                 scope.$evalAsync(function() {
213                     uiOnDragEnterFn(scope, {$event: e, $channel: dropChannel});
214                 });
215
216                 $rootScope.$broadcast('ANGULAR_HOVER', dragChannel);
217             }
218
219             function onDrop(e) {
220                 if (e.preventDefault) {
221                     e.preventDefault(); // Necessary. Allows us to drop.
222                 }
223                 if (e.stopPropagation) {
224                     e.stopPropagation(); // Necessary. Allows us to drop.
225                 }
226
227                 var sendData = e.dataTransfer.getData('text');
228                 sendData = angular.fromJson(sendData);
229
230                 determineEffectAllowed(e);
231
232                 var uiOnDropFn = $parse(attr.uiOnDrop);
233                 scope.$evalAsync(function() {
234                     uiOnDropFn(scope, {$data: sendData.data, $event: e, $channel: sendData.channel});
235                 });
236                 element.removeClass(dragEnterClass);
237                 dragging = 0;
238             }
239
240             function isDragChannelAccepted(dragChannel, dropChannel) {
241                 if (dropChannel === '*') {
242                     return true;
243                 }
244
245                 var channelMatchPattern = new RegExp('(\\s|[,])+(' + dragChannel + ')(\\s|[,])+', 'i');
246
247                 return channelMatchPattern.test(',' + dropChannel + ',');
248             }
249
250             function preventNativeDnD(e) {
251                 if (e.preventDefault) {
252                     e.preventDefault();
253                 }
254                 if (e.stopPropagation) {
255                     e.stopPropagation();
256                 }
257                 e.dataTransfer.dropEffect = 'none';
258                 return false;
259             }
260
261             var deregisterDragStart = $rootScope.$on('ANGULAR_DRAG_START', function(_, e, channel, transferDataObject) {
262                 dragChannel = channel;
263
264                 var valid = true;
265
266                 if (!isDragChannelAccepted(channel, dropChannel)) {
267                     valid = false;
268                 }
269
270                 if (valid && attr.dropValidate) {
271                     var validateFn = $parse(attr.dropValidate);
272                     valid = validateFn(scope, {
273                         $drop: {scope: scope, element: element},
274                         $event: e,
275                         $data: transferDataObject.data,
276                         $channel: transferDataObject.channel
277                     });
278                 }
279
280                 if (valid) {
281                     element.bind('dragover', onDragOver);
282                     element.bind('dragenter', onDragEnter);
283                     element.bind('dragleave', onDragLeave);
284                     element.bind('drop', onDrop);
285
286                     element.addClass(dragEnterClass);
287                 } else {
288                     element.bind('dragover', preventNativeDnD);
289                     element.bind('dragenter', preventNativeDnD);
290                     element.bind('dragleave', preventNativeDnD);
291                     element.bind('drop', preventNativeDnD);
292
293                     element.removeClass(dragEnterClass);
294                 }
295
296             });
297
298
299             var deregisterDragEnd = $rootScope.$on('ANGULAR_DRAG_END', function() {
300                 element.unbind('dragover', onDragOver);
301                 element.unbind('dragenter', onDragEnter);
302                 element.unbind('dragleave', onDragLeave);
303
304                 element.unbind('drop', onDrop);
305                 element.removeClass(dragHoverClass);
306                 element.removeClass(dragEnterClass);
307
308                 element.unbind('dragover', preventNativeDnD);
309                 element.unbind('dragenter', preventNativeDnD);
310                 element.unbind('dragleave', preventNativeDnD);
311                 element.unbind('drop', preventNativeDnD);
312             });
313
314             scope.$on('$destroy', function() {
315                 deregisterDragStart();
316                 deregisterDragEnd();
317             });
318
319
320             attr.$observe('dropChannel', function(value) {
321                 if (value) {
322                     dropChannel = value;
323                 }
324             });
325
326
327         };
328     }
329     ]);
330
331     module.constant('$dragImageConfig', {
332         height: 20,
333         width: 200,
334         padding: 10,
335         font: 'bold 11px Arial',
336         fontColor: '#eee8d5',
337         backgroundColor: '#93a1a1',
338         xOffset: 0,
339         yOffset: 0
340     });
341
342     module.service('$dragImage', ['$dragImageConfig', function(defaultConfig) {
343         var ELLIPSIS = '…';
344
345         function fitString(canvas, text, config) {
346             var width = canvas.measureText(text).width;
347             if (width < config.width) {
348                 return text;
349             }
350             while (width + config.padding > config.width) {
351                 text = text.substring(0, text.length - 1);
352                 width = canvas.measureText(text + ELLIPSIS).width;
353             }
354             return text + ELLIPSIS;
355         }
356
357         this.generate = function(text, options) {
358             var config = angular.extend({}, defaultConfig, options || {});
359             var el = document.createElement('canvas');
360
361             el.height = config.height;
362             el.width = config.width;
363
364             var canvas = el.getContext('2d');
365
366             canvas.fillStyle = config.backgroundColor;
367             canvas.fillRect(0, 0, config.width, config.height);
368             canvas.font = config.font;
369             canvas.fillStyle = config.fontColor;
370
371             var title = fitString(canvas, text, config);
372             canvas.fillText(title, 4, config.padding + 4);
373
374             var image = new Image();
375             image.src = el.toDataURL();
376
377             return {
378                 image: image,
379                 xOffset: config.xOffset,
380                 yOffset: config.yOffset
381             };
382         };
383     }
384     ]);
385
386 }(angular));