[VID-3] Setting docker image tag
[vid.git] / vid / src / main / webapp / app / vid / scripts / angular-ui-tree.js
1 /**
2  * @license Angular UI Tree v2.17.0
3  * (c) 2010-2016. https://github.com/angular-ui-tree/angular-ui-tree
4  * License: MIT
5  */
6 (function () {
7   'use strict';
8
9   angular.module('ui.tree', [])
10     .constant('treeConfig', {
11       treeClass: 'angular-ui-tree',
12       emptyTreeClass: 'angular-ui-tree-empty',
13       hiddenClass: 'angular-ui-tree-hidden',
14       nodesClass: 'angular-ui-tree-nodes',
15       nodeClass: 'angular-ui-tree-node',
16       handleClass: 'angular-ui-tree-handle',
17       placeholderClass: 'angular-ui-tree-placeholder',
18       dragClass: 'angular-ui-tree-drag',
19       dragThreshold: 3,
20       levelThreshold: 30,
21       defaultCollapsed: false
22     });
23
24 })();
25
26 (function () {
27   'use strict';
28
29   angular.module('ui.tree')
30
31     .controller('TreeHandleController', ['$scope', '$element',
32       function ($scope, $element) {
33         this.scope = $scope;
34
35         $scope.$element = $element;
36         $scope.$nodeScope = null;
37         $scope.$type = 'uiTreeHandle';
38
39       }
40     ]);
41 })();
42
43 (function () {
44   'use strict';
45
46   angular.module('ui.tree')
47     .controller('TreeNodeController', ['$scope', '$element',
48       function ($scope, $element) {
49         this.scope = $scope;
50
51         $scope.$element = $element;
52         $scope.$modelValue = null; // Model value for node;
53         $scope.$parentNodeScope = null; // uiTreeNode Scope of parent node;
54         $scope.$childNodesScope = null; // uiTreeNodes Scope of child nodes.
55         $scope.$parentNodesScope = null; // uiTreeNodes Scope of parent nodes.
56         $scope.$treeScope = null; // uiTree scope
57         $scope.$handleScope = null; // it's handle scope
58         $scope.$type = 'uiTreeNode';
59         $scope.$$allowNodeDrop = false;
60         $scope.collapsed = false;
61         $scope.expandOnHover = false;
62
63         $scope.init = function (controllersArr) {
64           var treeNodesCtrl = controllersArr[0];
65           $scope.$treeScope = controllersArr[1] ? controllersArr[1].scope : null;
66
67           // find the scope of it's parent node
68           $scope.$parentNodeScope = treeNodesCtrl.scope.$nodeScope;
69           // modelValue for current node
70           $scope.$modelValue = treeNodesCtrl.scope.$modelValue[$scope.$index];
71           $scope.$parentNodesScope = treeNodesCtrl.scope;
72           treeNodesCtrl.scope.initSubNode($scope); // init sub nodes
73
74           $element.on('$destroy', function () {
75             treeNodesCtrl.scope.destroySubNode($scope); // destroy sub nodes
76           });
77         };
78
79         $scope.index = function () {
80           return $scope.$parentNodesScope.$modelValue.indexOf($scope.$modelValue);
81         };
82
83         $scope.dragEnabled = function () {
84           return !($scope.$treeScope && !$scope.$treeScope.dragEnabled);
85         };
86
87         $scope.isSibling = function (targetNode) {
88           return $scope.$parentNodesScope == targetNode.$parentNodesScope;
89         };
90
91         $scope.isChild = function (targetNode) {
92           var nodes = $scope.childNodes();
93           return nodes && nodes.indexOf(targetNode) > -1;
94         };
95
96         $scope.prev = function () {
97           var index = $scope.index();
98           if (index > 0) {
99             return $scope.siblings()[index - 1];
100           }
101           return null;
102         };
103
104         $scope.siblings = function () {
105           return $scope.$parentNodesScope.childNodes();
106         };
107
108         $scope.childNodesCount = function () {
109           return $scope.childNodes() ? $scope.childNodes().length : 0;
110         };
111
112         $scope.hasChild = function () {
113           return $scope.childNodesCount() > 0;
114         };
115
116         $scope.childNodes = function () {
117           return $scope.$childNodesScope && $scope.$childNodesScope.$modelValue ?
118             $scope.$childNodesScope.childNodes() :
119             null;
120         };
121
122         $scope.accept = function (sourceNode, destIndex) {
123           return $scope.$childNodesScope &&
124             $scope.$childNodesScope.$modelValue &&
125             $scope.$childNodesScope.accept(sourceNode, destIndex);
126         };
127
128         $scope.remove = function () {
129           return $scope.$parentNodesScope.removeNode($scope);
130         };
131
132         $scope.toggle = function () {
133           $scope.collapsed = !$scope.collapsed;
134           $scope.$treeScope.$callbacks.toggle($scope.collapsed, $scope);
135         };
136
137         $scope.collapse = function () {
138           $scope.collapsed = true;
139         };
140
141         $scope.expand = function () {
142           $scope.collapsed = false;
143         };
144
145         $scope.depth = function () {
146           var parentNode = $scope.$parentNodeScope;
147           if (parentNode) {
148             return parentNode.depth() + 1;
149           }
150           return 1;
151         };
152
153         /**
154         * Returns the depth of the deepest subtree under this node
155         * @param scope a TreeNodesController scope object
156         * @returns Depth of all nodes *beneath* this node. If scope belongs to a leaf node, the
157         *   result is 0 (it has no subtree).
158         */
159         function countSubTreeDepth(scope) {
160           var thisLevelDepth = 0,
161               childNodes = scope.childNodes(),
162               childNode,
163               childDepth,
164               i;
165           if (!childNodes || childNodes.length === 0) {
166             return 0;
167           }
168           for (i = childNodes.length - 1; i >= 0 ; i--) {
169             childNode = childNodes[i],
170             childDepth = 1 + countSubTreeDepth(childNode);
171             thisLevelDepth = Math.max(thisLevelDepth, childDepth);
172           }
173           return thisLevelDepth;
174         }
175
176         $scope.maxSubDepth = function () {
177           return $scope.$childNodesScope ? countSubTreeDepth($scope.$childNodesScope) : 0;
178         };
179       }
180     ]);
181 })();
182
183 (function () {
184   'use strict';
185
186   angular.module('ui.tree')
187
188     .controller('TreeNodesController', ['$scope', '$element',
189       function ($scope, $element) {
190         this.scope = $scope;
191
192         $scope.$element = $element;
193         $scope.$modelValue = null;
194         $scope.$nodeScope = null; // the scope of node which the nodes belongs to
195         $scope.$treeScope = null;
196         $scope.$type = 'uiTreeNodes';
197         $scope.$nodesMap = {};
198
199         $scope.nodropEnabled = false;
200         $scope.maxDepth = 0;
201         $scope.cloneEnabled = false;
202
203         $scope.initSubNode = function (subNode) {
204           if (!subNode.$modelValue) {
205             return null;
206           }
207           $scope.$nodesMap[subNode.$modelValue.$$hashKey] = subNode;
208         };
209
210         $scope.destroySubNode = function (subNode) {
211           if (!subNode.$modelValue) {
212             return null;
213           }
214           $scope.$nodesMap[subNode.$modelValue.$$hashKey] = null;
215         };
216
217         $scope.accept = function (sourceNode, destIndex) {
218           return $scope.$treeScope.$callbacks.accept(sourceNode, $scope, destIndex);
219         };
220
221         $scope.beforeDrag = function (sourceNode) {
222           return $scope.$treeScope.$callbacks.beforeDrag(sourceNode);
223         };
224
225         $scope.isParent = function (node) {
226           return node.$parentNodesScope == $scope;
227         };
228
229         $scope.hasChild = function () {
230           return $scope.$modelValue.length > 0;
231         };
232
233         $scope.safeApply = function (fn) {
234           var phase = this.$root.$$phase;
235           if (phase == '$apply' || phase == '$digest') {
236             if (fn && (typeof (fn) === 'function')) {
237               fn();
238             }
239           } else {
240             this.$apply(fn);
241           }
242         };
243
244         $scope.removeNode = function (node) {
245           var index = $scope.$modelValue.indexOf(node.$modelValue);
246           if (index > -1) {
247             $scope.safeApply(function () {
248               $scope.$modelValue.splice(index, 1)[0];
249             });
250             return $scope.$treeScope.$callbacks.removed(node);
251           }
252           return null;
253         };
254
255         $scope.insertNode = function (index, nodeData) {
256           $scope.safeApply(function () {
257             $scope.$modelValue.splice(index, 0, nodeData);
258           });
259         };
260
261         $scope.childNodes = function () {
262           var i, nodes = [];
263           if ($scope.$modelValue) {
264             for (i = 0; i < $scope.$modelValue.length; i++) {
265               nodes.push($scope.$nodesMap[$scope.$modelValue[i].$$hashKey]);
266             }
267           }
268           return nodes;
269         };
270
271         $scope.depth = function () {
272           if ($scope.$nodeScope) {
273             return $scope.$nodeScope.depth();
274           }
275           return 0; // if it has no $nodeScope, it's root
276         };
277
278         // check if depth limit has reached
279         $scope.outOfDepth = function (sourceNode) {
280           var maxDepth = $scope.maxDepth || $scope.$treeScope.maxDepth;
281           if (maxDepth > 0) {
282             return $scope.depth() + sourceNode.maxSubDepth() + 1 > maxDepth;
283           }
284           return false;
285         };
286
287       }
288     ]);
289 })();
290
291 (function () {
292   'use strict';
293
294   angular.module('ui.tree')
295
296     .controller('TreeController', ['$scope', '$element',
297       function ($scope, $element) {
298         this.scope = $scope;
299
300         $scope.$element = $element;
301         $scope.$nodesScope = null; // root nodes
302         $scope.$type = 'uiTree';
303         $scope.$emptyElm = null;
304         $scope.$callbacks = null;
305
306         $scope.dragEnabled = true;
307         $scope.emptyPlaceholderEnabled = true;
308         $scope.maxDepth = 0;
309         $scope.dragDelay = 0;
310         $scope.cloneEnabled = false;
311         $scope.nodropEnabled = false;
312
313         // Check if it's a empty tree
314         $scope.isEmpty = function () {
315           return ($scope.$nodesScope && $scope.$nodesScope.$modelValue
316           && $scope.$nodesScope.$modelValue.length === 0);
317         };
318
319         // add placeholder to empty tree
320         $scope.place = function (placeElm) {
321           $scope.$nodesScope.$element.append(placeElm);
322           $scope.$emptyElm.remove();
323         };
324
325         this.resetEmptyElement = function () {
326           if ((!$scope.$nodesScope.$modelValue || $scope.$nodesScope.$modelValue.length === 0) &&
327             $scope.emptyPlaceholderEnabled) {
328             $element.append($scope.$emptyElm);
329           } else {
330             $scope.$emptyElm.remove();
331           }
332         };
333
334         $scope.resetEmptyElement = this.resetEmptyElement;
335       }
336     ]);
337 })();
338
339 (function () {
340   'use strict';
341
342   angular.module('ui.tree')
343     .directive('uiTree', ['treeConfig', '$window',
344       function (treeConfig, $window) {
345         return {
346           restrict: 'A',
347           scope: true,
348           controller: 'TreeController',
349           link: function (scope, element, attrs, ctrl) {
350             var callbacks = {
351               accept: null,
352               beforeDrag: null
353             },
354               config = {},
355               tdElm,
356               $trElm,
357               emptyElmColspan;
358
359             angular.extend(config, treeConfig);
360             if (config.treeClass) {
361               element.addClass(config.treeClass);
362             }
363
364             if (element.prop('tagName').toLowerCase() === 'table') {
365               scope.$emptyElm = angular.element($window.document.createElement('tr'));
366               $trElm = element.find('tr');
367               // If we can find a tr, then we can use its td children as the empty element colspan.
368               if ($trElm.length > 0) {
369                 emptyElmColspan = angular.element($trElm).children().length;
370               } else {
371                 // If not, by setting a huge colspan we make sure it takes full width.
372                 emptyElmColspan = 1000000;
373               }
374               tdElm = angular.element($window.document.createElement('td'))
375                 .attr('colspan', emptyElmColspan);
376               scope.$emptyElm.append(tdElm);
377             } else {
378               scope.$emptyElm = angular.element($window.document.createElement('div'));
379             }
380
381             if (config.emptyTreeClass) {
382               scope.$emptyElm.addClass(config.emptyTreeClass);
383             }
384
385             scope.$watch('$nodesScope.$modelValue.length', function (val) {
386               if (!angular.isNumber(val)) {
387                 return;
388               }
389
390               ctrl.resetEmptyElement();
391             }, true);
392
393             scope.$watch(attrs.dragEnabled, function (val) {
394               if ((typeof val) == 'boolean') {
395                 scope.dragEnabled = val;
396               }
397             });
398
399             scope.$watch(attrs.emptyPlaceholderEnabled, function (val) {
400               if ((typeof val) == 'boolean') {
401                 scope.emptyPlaceholderEnabled = val;
402                 ctrl.resetEmptyElement();
403               }
404             });
405
406             scope.$watch(attrs.nodropEnabled, function (val) {
407               if ((typeof val) == 'boolean') {
408                 scope.nodropEnabled = val;
409               }
410             });
411
412             scope.$watch(attrs.cloneEnabled, function (val) {
413               if ((typeof val) == 'boolean') {
414                 scope.cloneEnabled = val;
415               }
416             });
417
418             scope.$watch(attrs.maxDepth, function (val) {
419               if ((typeof val) == 'number') {
420                 scope.maxDepth = val;
421               }
422             });
423
424             scope.$watch(attrs.dragDelay, function (val) {
425               if ((typeof val) == 'number') {
426                 scope.dragDelay = val;
427               }
428             });
429
430             /**
431              * Callback checks if the destination node can accept the dragged node.
432              * By default, ui-tree will check that 'data-nodrop-enabled' is not set for the
433              * destination ui-tree-nodes, and that the 'max-depth' attribute will not be exceeded
434              * if it is set on the ui-tree or ui-tree-nodes.
435              * This callback can be overridden, but callers must manually enforce nodrop and max-depth
436              * themselves if they need those to be enforced.
437              * @param sourceNodeScope Scope of the ui-tree-node being dragged
438              * @param destNodesScope Scope of the ui-tree-nodes where the node is hovering
439              * @param destIndex Index in the destination nodes array where the source node will drop
440              * @returns {boolean} True if the node is permitted to be dropped here
441              */
442             callbacks.accept = function (sourceNodeScope, destNodesScope, destIndex) {
443               return !(destNodesScope.nodropEnabled || destNodesScope.$treeScope.nodropEnabled || destNodesScope.outOfDepth(sourceNodeScope));
444             };
445
446             callbacks.beforeDrag = function (sourceNodeScope) {
447               return true;
448             };
449
450             callbacks.expandTimeoutStart = function()
451             {
452
453             };
454
455             callbacks.expandTimeoutCancel = function()
456             {
457
458             };
459
460             callbacks.expandTimeoutEnd = function()
461             {
462
463             };
464
465             callbacks.removed = function (node) {
466
467             };
468
469             /**
470              * Callback is fired when a node is successfully dropped in a new location
471              * @param event
472              */
473             callbacks.dropped = function (event) {
474
475             };
476
477             /**
478              * Callback is fired each time the user starts dragging a node
479              * @param event
480              */
481             callbacks.dragStart = function (event) {
482
483             };
484
485             /**
486              * Callback is fired each time a dragged node is moved with the mouse/touch.
487              * @param event
488              */
489             callbacks.dragMove = function (event) {
490
491             };
492
493             /**
494              * Callback is fired when the tree exits drag mode. If the user dropped a node, the drop may have been
495              * accepted or reverted.
496              * @param event
497              */
498             callbacks.dragStop = function (event) {
499
500             };
501
502             /**
503              * Callback is fired when a user drops a node (but prior to processing the drop action)
504              * beforeDrop can return a Promise, truthy, or falsy (returning nothing is falsy).
505              * If it returns falsy, or a resolve Promise, the node move is accepted
506              * If it returns truthy, or a rejected Promise, the node move is reverted
507              * @param event
508              * @returns {Boolean|Promise} Truthy (or rejected Promise) to cancel node move; falsy (or resolved promise)
509              */
510             callbacks.beforeDrop = function (event) {
511
512             };
513
514             /**
515              * Callback is fired when a user toggles node (but after processing the toggle action)
516              * @param sourceNodeScope
517              * @param collapsed
518              */
519             callbacks.toggle = function (collapsed, sourceNodeScope) {
520
521             };
522
523             scope.$watch(attrs.uiTree, function (newVal, oldVal) {
524               angular.forEach(newVal, function (value, key) {
525                 if (callbacks[key]) {
526                   if (typeof value === 'function') {
527                     callbacks[key] = value;
528                   }
529                 }
530               });
531
532               scope.$callbacks = callbacks;
533             }, true);
534
535
536           }
537         };
538       }
539     ]);
540 })();
541
542 (function () {
543   'use strict';
544
545   angular.module('ui.tree')
546     .directive('uiTreeHandle', ['treeConfig',
547       function (treeConfig) {
548         return {
549           require: '^uiTreeNode',
550           restrict: 'A',
551           scope: true,
552           controller: 'TreeHandleController',
553           link: function (scope, element, attrs, treeNodeCtrl) {
554             var config = {};
555             angular.extend(config, treeConfig);
556             if (config.handleClass) {
557               element.addClass(config.handleClass);
558             }
559             // connect with the tree node.
560             if (scope != treeNodeCtrl.scope) {
561               scope.$nodeScope = treeNodeCtrl.scope;
562               treeNodeCtrl.scope.$handleScope = scope;
563             }
564           }
565         };
566       }
567     ]);
568 })();
569
570 (function () {
571   'use strict';
572
573   angular.module('ui.tree')
574
575     .directive('uiTreeNode', ['treeConfig', 'UiTreeHelper', '$window', '$document', '$timeout', '$q',
576       function (treeConfig, UiTreeHelper, $window, $document, $timeout, $q) {
577         return {
578           require: ['^uiTreeNodes', '^uiTree'],
579           restrict: 'A',
580           controller: 'TreeNodeController',
581           link: function (scope, element, attrs, controllersArr) {
582             // todo startPos is unused
583             var config = {},
584               hasTouch = 'ontouchstart' in window,
585               startPos, firstMoving, dragInfo, pos,
586               placeElm, hiddenPlaceElm, dragElm,
587               treeScope = null,
588               elements, // As a parameter for callbacks
589               dragDelaying = true,
590               dragStarted = false,
591               dragTimer = null,
592               body = document.body,
593               html = document.documentElement,
594               document_height,
595               document_width,
596               dragStart,
597               tagName,
598               dragMove,
599               dragEnd,
600               dragStartEvent,
601               dragMoveEvent,
602               dragEndEvent,
603               dragCancelEvent,
604               dragDelay,
605               bindDragStartEvents,
606               bindDragMoveEvents,
607               unbindDragMoveEvents,
608               keydownHandler,
609               outOfBounds,
610               isHandleChild,
611               el;
612
613             angular.extend(config, treeConfig);
614             if (config.nodeClass) {
615               element.addClass(config.nodeClass);
616             }
617             scope.init(controllersArr);
618
619             scope.collapsed = !!UiTreeHelper.getNodeAttribute(scope, 'collapsed') || treeConfig.defaultCollapsed;
620                         scope.expandOnHover = !!UiTreeHelper.getNodeAttribute(scope, 'expandOnHover');
621             scope.sourceOnly = scope.nodropEnabled || scope.$treeScope.nodropEnabled;
622
623             scope.$watch(attrs.collapsed, function (val) {
624               if ((typeof val) == 'boolean') {
625                 scope.collapsed = val;
626               }
627             });
628
629             scope.$watch('collapsed', function (val) {
630               UiTreeHelper.setNodeAttribute(scope, 'collapsed', val);
631               attrs.$set('collapsed', val);
632             });
633
634             scope.$watch(attrs.expandOnHover, function(val) {
635               if ((typeof val) == 'boolean') {
636                 scope.expandOnHover = val;
637               }
638             });
639
640                         scope.$watch('expandOnHover', function (val) {
641               UiTreeHelper.setNodeAttribute(scope, 'expandOnHover', val);
642               attrs.$set('expandOnHover', val);
643             });
644
645             scope.$on('angular-ui-tree:collapse-all', function () {
646               scope.collapsed = true;
647             });
648
649             scope.$on('angular-ui-tree:expand-all', function () {
650               scope.collapsed = false;
651             });
652
653             /**
654              * Called when the user has grabbed a node and started dragging it
655              * @param e
656              */
657             dragStart = function (e) {
658               // disable right click
659               if (!hasTouch && (e.button === 2 || e.which === 3)) {
660                 return;
661               }
662
663               // event has already fired in other scope
664               if (e.uiTreeDragging || (e.originalEvent && e.originalEvent.uiTreeDragging)) {
665                 return;
666               }
667
668               // the node being dragged
669               var eventElm = angular.element(e.target),
670                 isHandleChild, cloneElm, eventElmTagName, tagName,
671                 eventObj, tdElm, hStyle,
672                 isTreeNode,
673                 isTreeNodeHandle;
674
675               // if the target element is a child element of a ui-tree-handle,
676               // use the containing handle element as target element
677               isHandleChild = UiTreeHelper.treeNodeHandlerContainerOfElement(eventElm);
678               if (isHandleChild) {
679                 eventElm = angular.element(isHandleChild);
680               }
681
682               cloneElm = element.clone();
683               isTreeNode = UiTreeHelper.elementIsTreeNode(eventElm);
684               isTreeNodeHandle = UiTreeHelper.elementIsTreeNodeHandle(eventElm);
685
686               if (!isTreeNode && !isTreeNodeHandle) {
687                 return;
688               }
689
690               if (isTreeNode && UiTreeHelper.elementContainsTreeNodeHandler(eventElm)) {
691                 return;
692               }
693
694               eventElmTagName = eventElm.prop('tagName').toLowerCase();
695               if (eventElmTagName == 'input' ||
696                 eventElmTagName == 'textarea' ||
697                 eventElmTagName == 'button' ||
698                 eventElmTagName == 'select') { // if it's a input or button, ignore it
699                 return;
700               }
701
702               // check if it or it's parents has a 'data-nodrag' attribute
703               el = angular.element(e.target);
704               while (el && el[0] && el[0] !== element) {
705                 if (UiTreeHelper.nodrag(el)) { // if the node mark as `nodrag`, DONOT drag it.
706                   return;
707                 }
708                 el = el.parent();
709               }
710
711               if (!scope.beforeDrag(scope)) {
712                 return;
713               }
714
715               e.uiTreeDragging = true; // stop event bubbling
716               if (e.originalEvent) {
717                 e.originalEvent.uiTreeDragging = true;
718               }
719               e.preventDefault();
720               eventObj = UiTreeHelper.eventObj(e);
721
722               firstMoving = true;
723               dragInfo = UiTreeHelper.dragInfo(scope);
724
725               tagName = element.prop('tagName');
726
727               if (tagName.toLowerCase() === 'tr') {
728                 placeElm = angular.element($window.document.createElement(tagName));
729                 tdElm = angular.element($window.document.createElement('td'))
730                   .addClass(config.placeholderClass)
731                   .attr('colspan', element[0].children.length);
732                 placeElm.append(tdElm);
733               } else {
734                 placeElm = angular.element($window.document.createElement(tagName))
735                   .addClass(config.placeholderClass);
736               }
737               hiddenPlaceElm = angular.element($window.document.createElement(tagName));
738               if (config.hiddenClass) {
739                 hiddenPlaceElm.addClass(config.hiddenClass);
740               }
741
742               pos = UiTreeHelper.positionStarted(eventObj, element);
743               placeElm.css('height', UiTreeHelper.height(element) + 'px');
744
745               dragElm = angular.element($window.document.createElement(scope.$parentNodesScope.$element.prop('tagName')))
746                 .addClass(scope.$parentNodesScope.$element.attr('class')).addClass(config.dragClass);
747               dragElm.css('width', UiTreeHelper.width(element) + 'px');
748               dragElm.css('z-index', 9999);
749
750               // Prevents cursor to change rapidly in Opera 12.16 and IE when dragging an element
751               hStyle = (element[0].querySelector('.angular-ui-tree-handle') || element[0]).currentStyle;
752               if (hStyle) {
753                 document.body.setAttribute('ui-tree-cursor', $document.find('body').css('cursor') || '');
754                 $document.find('body').css({'cursor': hStyle.cursor + '!important'});
755               }
756
757               if (scope.sourceOnly) {
758                 placeElm.css('display', 'none');
759               }
760               element.after(placeElm);
761               element.after(hiddenPlaceElm);
762               if (dragInfo.isClone() && scope.sourceOnly) {
763                 dragElm.append(cloneElm);
764               } else {
765                 dragElm.append(element);
766               }
767
768               $document.find('body').append(dragElm);
769
770               dragElm.css({
771                 'left': eventObj.pageX - pos.offsetX + 'px',
772                 'top': eventObj.pageY - pos.offsetY + 'px'
773               });
774               elements = {
775                 placeholder: placeElm,
776                 dragging: dragElm
777               };
778
779               bindDragMoveEvents();
780               // Fire dragStart callback
781               scope.$apply(function () {
782                 scope.$treeScope.$callbacks.dragStart(dragInfo.eventArgs(elements, pos));
783               });
784
785               document_height = Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight);
786               document_width = Math.max(body.scrollWidth, body.offsetWidth, html.clientWidth, html.scrollWidth, html.offsetWidth);
787             };
788
789             dragMove = function (e) {
790               var eventObj = UiTreeHelper.eventObj(e),
791                 prev,
792                 next,
793                 leftElmPos,
794                 topElmPos,
795                 top_scroll,
796                 bottom_scroll,
797                 target,
798                 decrease,
799                 targetX,
800                 targetY,
801                 displayElm,
802                 targetNode,
803                 targetElm,
804                 isEmpty,
805                 scrollDownBy,
806                 targetOffset,
807                 targetBefore;
808
809               if (dragElm) {
810                 e.preventDefault();
811
812                 if ($window.getSelection) {
813                   $window.getSelection().removeAllRanges();
814                 } else if ($window.document.selection) {
815                   $window.document.selection.empty();
816                 }
817
818                 leftElmPos = eventObj.pageX - pos.offsetX;
819                 topElmPos = eventObj.pageY - pos.offsetY;
820
821                 //dragElm can't leave the screen on the left
822                 if (leftElmPos < 0) {
823                   leftElmPos = 0;
824                 }
825
826                 //dragElm can't leave the screen on the top
827                 if (topElmPos < 0) {
828                   topElmPos = 0;
829                 }
830
831                 //dragElm can't leave the screen on the bottom
832                 if ((topElmPos + 10) > document_height) {
833                   topElmPos = document_height - 10;
834                 }
835
836                 //dragElm can't leave the screen on the right
837                 if ((leftElmPos + 10) > document_width) {
838                   leftElmPos = document_width - 10;
839                 }
840
841                 dragElm.css({
842                   'left': leftElmPos + 'px',
843                   'top': topElmPos + 'px'
844                 });
845
846                 top_scroll = window.pageYOffset || $window.document.documentElement.scrollTop;
847                 bottom_scroll = top_scroll + (window.innerHeight || $window.document.clientHeight || $window.document.clientHeight);
848
849                 // to scroll down if cursor y-position is greater than the bottom position the vertical scroll
850                 if (bottom_scroll < eventObj.pageY && bottom_scroll < document_height) {
851                   scrollDownBy = Math.min(document_height - bottom_scroll, 10);
852                   window.scrollBy(0, scrollDownBy);
853                 }
854
855                 // to scroll top if cursor y-position is less than the top position the vertical scroll
856                 if (top_scroll > eventObj.pageY) {
857                   window.scrollBy(0, -10);
858                 }
859
860                 UiTreeHelper.positionMoved(e, pos, firstMoving);
861                 if (firstMoving) {
862                   firstMoving = false;
863                   return;
864                 }
865
866                 // check if add it as a child node first
867                 // todo decrease is unused
868                 decrease = (UiTreeHelper.offset(dragElm).left - UiTreeHelper.offset(placeElm).left) >= config.threshold;
869
870                 targetX = eventObj.pageX - ($window.pageXOffset ||
871                   $window.document.body.scrollLeft ||
872                   $window.document.documentElement.scrollLeft) -
873                   ($window.document.documentElement.clientLeft || 0);
874
875                 targetY = eventObj.pageY - ($window.pageYOffset ||
876                   $window.document.body.scrollTop ||
877                   $window.document.documentElement.scrollTop) -
878                   ($window.document.documentElement.clientTop || 0);
879
880                 // Select the drag target. Because IE does not support CSS 'pointer-events: none', it will always
881                 // pick the drag element itself as the target. To prevent this, we hide the drag element while
882                 // selecting the target.
883                 if (angular.isFunction(dragElm.hide)) {
884                   dragElm.hide();
885                 } else {
886                   displayElm = dragElm[0].style.display;
887                   dragElm[0].style.display = 'none';
888                 }
889
890                 // when using elementFromPoint() inside an iframe, you have to call
891                 // elementFromPoint() twice to make sure IE8 returns the correct value
892                 $window.document.elementFromPoint(targetX, targetY);
893
894                 targetElm = angular.element($window.document.elementFromPoint(targetX, targetY));
895
896                 // if the target element is a child element of a ui-tree-handle,
897                 // use the containing handle element as target element
898                 isHandleChild = UiTreeHelper.treeNodeHandlerContainerOfElement(targetElm);
899                 if (isHandleChild) {
900                   targetElm = angular.element(isHandleChild);
901                 }
902
903                 if (angular.isFunction(dragElm.show)) {
904                   dragElm.show();
905                 } else {
906                   dragElm[0].style.display = displayElm;
907                 }
908
909                 outOfBounds = !UiTreeHelper.elementIsTreeNodeHandle(targetElm) &&
910                               !UiTreeHelper.elementIsTreeNode(targetElm) &&
911                               !UiTreeHelper.elementIsTreeNodes(targetElm) &&
912                               !UiTreeHelper.elementIsTree(targetElm) &&
913                               !UiTreeHelper.elementIsPlaceholder(targetElm);
914
915                 // Detect out of bounds condition, update drop target display, and prevent drop
916                 if (outOfBounds) {
917
918                   // Remove the placeholder
919                   placeElm.remove();
920
921                   // If the target was an empty tree, replace the empty element placeholder
922                   if (treeScope) {
923                     treeScope.resetEmptyElement();
924                     treeScope = null;
925                   }
926                 }
927
928                 // move horizontal
929                 if (pos.dirAx && pos.distAxX >= config.levelThreshold) {
930                   pos.distAxX = 0;
931
932                   // increase horizontal level if previous sibling exists and is not collapsed
933                   if (pos.distX > 0) {
934                     prev = dragInfo.prev();
935                     if (prev && !prev.collapsed
936                       && prev.accept(scope, prev.childNodesCount())) {
937                       prev.$childNodesScope.$element.append(placeElm);
938                       dragInfo.moveTo(prev.$childNodesScope, prev.childNodes(), prev.childNodesCount());
939                     }
940                   }
941
942                   // decrease horizontal level
943                   if (pos.distX < 0) {
944                     // we can't decrease a level if an item preceeds the current one
945                     next = dragInfo.next();
946                     if (!next) {
947                       target = dragInfo.parentNode(); // As a sibling of it's parent node
948                       if (target
949                         && target.$parentNodesScope.accept(scope, target.index() + 1)) {
950                         target.$element.after(placeElm);
951                         dragInfo.moveTo(target.$parentNodesScope, target.siblings(), target.index() + 1);
952                       }
953                     }
954                   }
955                 }
956
957                 // move vertical
958                 if (!pos.dirAx) {
959                   if (UiTreeHelper.elementIsTree(targetElm)) {
960                     targetNode = targetElm.controller('uiTree').scope;
961                   } else if (UiTreeHelper.elementIsTreeNodeHandle(targetElm)) {
962                     targetNode = targetElm.controller('uiTreeHandle').scope;
963                   } else if (UiTreeHelper.elementIsTreeNode(targetElm)) {
964                     targetNode = targetElm.controller('uiTreeNode').scope;
965                   } else if (UiTreeHelper.elementIsTreeNodes(targetElm)) {
966                     targetNode = targetElm.controller('uiTreeNodes').scope;
967                   } else if (UiTreeHelper.elementIsPlaceholder(targetElm)) {
968                     targetNode = targetElm.controller('uiTreeNodes').scope;
969                   } else if (targetElm.controller('uiTreeNode')) {
970                     // is a child element of a node
971                     targetNode = targetElm.controller('uiTreeNode').scope;
972                   }
973
974                   // check it's new position
975                   isEmpty = false;
976                   if (!targetNode) {
977                     return;
978                   }
979
980                   // Show the placeholder if it was hidden for nodrop-enabled and this is a new tree
981                   if (targetNode.$treeScope && !targetNode.$parent.nodropEnabled && !targetNode.$treeScope.nodropEnabled) {
982                     placeElm.css('display', '');
983                   }
984
985                   if (targetNode.$type == 'uiTree' && targetNode.dragEnabled) {
986                     isEmpty = targetNode.isEmpty(); // Check if it's empty tree
987                   }
988
989                   if (targetNode.$type == 'uiTreeHandle') {
990                     targetNode = targetNode.$nodeScope;
991                   }
992
993                   if (targetNode.$type != 'uiTreeNode'
994                     && !isEmpty) { // Check if it is a uiTreeNode or it's an empty tree
995                     return;
996                   }
997
998                   // if placeholder move from empty tree, reset it.
999                   if (treeScope && placeElm.parent()[0] != treeScope.$element[0]) {
1000                     treeScope.resetEmptyElement();
1001                     treeScope = null;
1002                   }
1003
1004                   if (isEmpty) { // it's an empty tree
1005                     treeScope = targetNode;
1006                     if (targetNode.$nodesScope.accept(scope, 0)) {
1007                       targetNode.place(placeElm);
1008                       dragInfo.moveTo(targetNode.$nodesScope, targetNode.$nodesScope.childNodes(), 0);
1009                     }
1010                   } else if (targetNode.dragEnabled()) { // drag enabled
1011                       if (angular.isDefined(scope.expandTimeoutOn) && scope.expandTimeoutOn !== targetNode.id) {
1012                         $timeout.cancel(scope.expandTimeout);
1013                         delete scope.expandTimeout;
1014                         delete scope.expandTimeoutOn;
1015
1016                         scope.$callbacks.expandTimeoutCancel();
1017                       }
1018
1019                       if (targetNode.collapsed) {
1020                         if (scope.expandOnHover === true || (angular.isNumber(scope.expandOnHover) && scope.expandOnHover === 0)) {
1021                           targetNode.collapsed = false;
1022                         } else if (scope.expandOnHover !== false && angular.isNumber(scope.expandOnHover) && scope.expandOnHover > 0) {
1023                           if (angular.isUndefined(scope.expandTimeoutOn)) {
1024                             scope.expandTimeoutOn = targetNode.$id;
1025
1026                             scope.$callbacks.expandTimeoutStart();
1027                             scope.expandTimeout = $timeout(function()
1028                             {
1029                               scope.$callbacks.expandTimeoutEnd();
1030                               targetNode.collapsed = false;
1031                             }, scope.expandOnHover);
1032                           }
1033                         }
1034                       }
1035
1036                     targetElm = targetNode.$element; // Get the element of ui-tree-node
1037                     targetOffset = UiTreeHelper.offset(targetElm);
1038                     targetBefore = targetNode.horizontal ? eventObj.pageX < (targetOffset.left + UiTreeHelper.width(targetElm) / 2)
1039                       : eventObj.pageY < (targetOffset.top + UiTreeHelper.height(targetElm) / 2);
1040
1041                     if (targetNode.$parentNodesScope.accept(scope, targetNode.index())) {
1042                       if (targetBefore) {
1043                         targetElm[0].parentNode.insertBefore(placeElm[0], targetElm[0]);
1044                         dragInfo.moveTo(targetNode.$parentNodesScope, targetNode.siblings(), targetNode.index());
1045                       } else {
1046                         targetElm.after(placeElm);
1047                         dragInfo.moveTo(targetNode.$parentNodesScope, targetNode.siblings(), targetNode.index() + 1);
1048                       }
1049                     } else if (!targetBefore && targetNode.accept(scope, targetNode.childNodesCount())) { // we have to check if it can add the dragging node as a child
1050                       targetNode.$childNodesScope.$element.append(placeElm);
1051                       dragInfo.moveTo(targetNode.$childNodesScope, targetNode.childNodes(), targetNode.childNodesCount());
1052                     } else {
1053                       outOfBounds = true;
1054                     }
1055                   }
1056                 }
1057
1058                 scope.$apply(function () {
1059                   scope.$treeScope.$callbacks.dragMove(dragInfo.eventArgs(elements, pos));
1060                 });
1061               }
1062             };
1063
1064             dragEnd = function (e) {
1065               var dragEventArgs = dragInfo.eventArgs(elements, pos);
1066               e.preventDefault();
1067               unbindDragMoveEvents();
1068
1069               $timeout.cancel(scope.expandTimeout);
1070
1071               scope.$treeScope.$apply(function () {
1072                 $q.when(scope.$treeScope.$callbacks.beforeDrop(dragEventArgs))
1073                     // promise resolved (or callback didn't return false)
1074                     .then(function (allowDrop) {
1075                       if (allowDrop !== false && scope.$$allowNodeDrop && !outOfBounds) { // node drop accepted)
1076                         dragInfo.apply();
1077                         // fire the dropped callback only if the move was successful
1078                         scope.$treeScope.$callbacks.dropped(dragEventArgs);
1079                       } else { // drop canceled - revert the node to its original position
1080                         bindDragStartEvents();
1081                       }
1082                     })
1083                     // promise rejected - revert the node to its original position
1084                     .catch(function () {
1085                       bindDragStartEvents();
1086                     })
1087                     .finally(function () {
1088                       hiddenPlaceElm.replaceWith(scope.$element);
1089                       placeElm.remove();
1090
1091                       if (dragElm) { // drag element is attached to the mouse pointer
1092                         dragElm.remove();
1093                         dragElm = null;
1094                       }
1095                       scope.$treeScope.$callbacks.dragStop(dragEventArgs);
1096                       scope.$$allowNodeDrop = false;
1097                       dragInfo = null;
1098
1099                       // Restore cursor in Opera 12.16 and IE
1100                       var oldCur = document.body.getAttribute('ui-tree-cursor');
1101                       if (oldCur !== null) {
1102                         $document.find('body').css({'cursor': oldCur});
1103                         document.body.removeAttribute('ui-tree-cursor');
1104                       }
1105                     });
1106               });
1107             };
1108
1109             dragStartEvent = function (e) {
1110               if (scope.dragEnabled()) {
1111                 dragStart(e);
1112               }
1113             };
1114
1115             dragMoveEvent = function (e) {
1116               dragMove(e);
1117             };
1118
1119             dragEndEvent = function (e) {
1120               scope.$$allowNodeDrop = true;
1121               dragEnd(e);
1122             };
1123
1124             dragCancelEvent = function (e) {
1125               dragEnd(e);
1126             };
1127
1128             dragDelay = (function () {
1129               var to;
1130
1131               return {
1132                 exec: function (fn, ms) {
1133                   if (!ms) {
1134                     ms = 0;
1135                   }
1136                   this.cancel();
1137                   to = $timeout(fn, ms);
1138                 },
1139                 cancel: function () {
1140                   $timeout.cancel(to);
1141                 }
1142               };
1143             })();
1144
1145             /**
1146              * Binds the mouse/touch events to enable drag start for this node
1147              */
1148             bindDragStartEvents = function () {
1149               element.bind('touchstart mousedown', function (e) {
1150                 dragDelay.exec(function () {
1151                   dragStartEvent(e);
1152                 }, scope.dragDelay || 0);
1153               });
1154               element.bind('touchend touchcancel mouseup', function () {
1155                 dragDelay.cancel();
1156               });
1157             };
1158             bindDragStartEvents();
1159
1160             /**
1161              * Binds mouse/touch events that handle moving/dropping this dragged node
1162              */
1163             bindDragMoveEvents = function () {
1164               angular.element($document).bind('touchend', dragEndEvent);
1165               angular.element($document).bind('touchcancel', dragEndEvent);
1166               angular.element($document).bind('touchmove', dragMoveEvent);
1167               angular.element($document).bind('mouseup', dragEndEvent);
1168               angular.element($document).bind('mousemove', dragMoveEvent);
1169               angular.element($document).bind('mouseleave', dragCancelEvent);
1170             };
1171
1172             /**
1173              * Unbinds mouse/touch events that handle moving/dropping this dragged node
1174              */
1175             unbindDragMoveEvents = function () {
1176               angular.element($document).unbind('touchend', dragEndEvent);
1177               angular.element($document).unbind('touchcancel', dragEndEvent);
1178               angular.element($document).unbind('touchmove', dragMoveEvent);
1179               angular.element($document).unbind('mouseup', dragEndEvent);
1180               angular.element($document).unbind('mousemove', dragMoveEvent);
1181               angular.element($document).unbind('mouseleave', dragCancelEvent);
1182             };
1183
1184             keydownHandler = function (e) {
1185               if (e.keyCode == 27) {
1186                 scope.$$allowNodeDrop = false;
1187                 dragEnd(e);
1188               }
1189             };
1190
1191             angular.element($window.document).bind('keydown', keydownHandler);
1192
1193             //unbind handler that retains scope
1194             scope.$on('$destroy', function () {
1195               angular.element($window.document).unbind('keydown', keydownHandler);
1196             });
1197           }
1198         };
1199       }
1200     ]);
1201
1202 })();
1203
1204 (function () {
1205   'use strict';
1206
1207   angular.module('ui.tree')
1208     .directive('uiTreeNodes', ['treeConfig', '$window',
1209       function (treeConfig) {
1210         return {
1211           require: ['ngModel', '?^uiTreeNode', '^uiTree'],
1212           restrict: 'A',
1213           scope: true,
1214           controller: 'TreeNodesController',
1215           link: function (scope, element, attrs, controllersArr) {
1216
1217             var config = {},
1218                 ngModel = controllersArr[0],
1219                 treeNodeCtrl = controllersArr[1],
1220                 treeCtrl = controllersArr[2];
1221
1222             angular.extend(config, treeConfig);
1223             if (config.nodesClass) {
1224               element.addClass(config.nodesClass);
1225             }
1226
1227             if (treeNodeCtrl) {
1228               treeNodeCtrl.scope.$childNodesScope = scope;
1229               scope.$nodeScope = treeNodeCtrl.scope;
1230             } else {
1231               // find the root nodes if there is no parent node and have a parent ui-tree
1232               treeCtrl.scope.$nodesScope = scope;
1233             }
1234             scope.$treeScope = treeCtrl.scope;
1235
1236             if (ngModel) {
1237               ngModel.$render = function () {
1238                 scope.$modelValue = ngModel.$modelValue;
1239               };
1240             }
1241
1242             scope.$watch(function () {
1243               return attrs.maxDepth;
1244             }, function (val) {
1245               if ((typeof val) == 'number') {
1246                 scope.maxDepth = val;
1247               }
1248             });
1249
1250             scope.$watch(function () {
1251               return attrs.nodropEnabled;
1252             }, function (newVal) {
1253               if ((typeof newVal) != 'undefined') {
1254                 scope.nodropEnabled = true;
1255               }
1256             }, true);
1257
1258             attrs.$observe('horizontal', function (val) {
1259               scope.horizontal = ((typeof val) != 'undefined');
1260             });
1261
1262           }
1263         };
1264       }
1265     ]);
1266 })();
1267
1268 (function () {
1269   'use strict';
1270
1271   angular.module('ui.tree')
1272
1273   /**
1274    * @ngdoc service
1275    * @name ui.tree.service:UiTreeHelper
1276    * @requires ng.$document
1277    * @requires ng.$window
1278    *
1279    * @description
1280    * angular-ui-tree.
1281    */
1282     .factory('UiTreeHelper', ['$document', '$window', 'treeConfig',
1283       function ($document, $window, treeConfig) {
1284         return {
1285
1286           /**
1287            * A hashtable used to storage data of nodes
1288            * @type {Object}
1289            */
1290           nodesData: {},
1291
1292           setNodeAttribute: function (scope, attrName, val) {
1293             if (!scope.$modelValue) {
1294               return null;
1295             }
1296             var data = this.nodesData[scope.$modelValue.$$hashKey];
1297             if (!data) {
1298               data = {};
1299               this.nodesData[scope.$modelValue.$$hashKey] = data;
1300             }
1301             data[attrName] = val;
1302           },
1303
1304           getNodeAttribute: function (scope, attrName) {
1305             if (!scope.$modelValue) {
1306               return null;
1307             }
1308             var data = this.nodesData[scope.$modelValue.$$hashKey];
1309             if (data) {
1310               return data[attrName];
1311             }
1312             return null;
1313           },
1314
1315           /**
1316            * @ngdoc method
1317            * @methodOf ui.tree.service:$nodrag
1318            * @param  {Object} targetElm angular element
1319            * @return {Bool} check if the node can be dragged.
1320            */
1321           nodrag: function (targetElm) {
1322             if (typeof targetElm.attr('data-nodrag') != 'undefined') {
1323               return targetElm.attr('data-nodrag') !== 'false';
1324             }
1325             return false;
1326           },
1327
1328           /**
1329            * get the event object for touches
1330            * @param  {[type]} e [description]
1331            * @return {[type]}   [description]
1332            */
1333           eventObj: function (e) {
1334             var obj = e;
1335             if (e.targetTouches !== undefined) {
1336               obj = e.targetTouches.item(0);
1337             } else if (e.originalEvent !== undefined && e.originalEvent.targetTouches !== undefined) {
1338               obj = e.originalEvent.targetTouches.item(0);
1339             }
1340             return obj;
1341           },
1342
1343           dragInfo: function (node) {
1344             return {
1345               source: node,
1346               sourceInfo: {
1347                 cloneModel: node.$treeScope.cloneEnabled === true ? angular.copy(node.$modelValue) : undefined,
1348                 nodeScope: node,
1349                 index: node.index(),
1350                 nodesScope: node.$parentNodesScope
1351               },
1352               index: node.index(),
1353               siblings: node.siblings().slice(0),
1354               parent: node.$parentNodesScope,
1355
1356               // Move the node to a new position
1357               moveTo: function (parent, siblings, index) {
1358                 this.parent = parent;
1359                 this.siblings = siblings.slice(0);
1360
1361                 // If source node is in the target nodes
1362                 var i = this.siblings.indexOf(this.source);
1363                 if (i > -1) {
1364                   this.siblings.splice(i, 1);
1365                   if (this.source.index() < index) {
1366                     index--;
1367                   }
1368                 }
1369
1370                 this.siblings.splice(index, 0, this.source);
1371                 this.index = index;
1372               },
1373
1374               parentNode: function () {
1375                 return this.parent.$nodeScope;
1376               },
1377
1378               prev: function () {
1379                 if (this.index > 0) {
1380                   return this.siblings[this.index - 1];
1381                 }
1382
1383                 return null;
1384               },
1385
1386               next: function () {
1387                 if (this.index < this.siblings.length - 1) {
1388                   return this.siblings[this.index + 1];
1389                 }
1390
1391                 return null;
1392               },
1393
1394               isClone: function () {
1395                 return this.source.$treeScope.cloneEnabled === true;
1396               },
1397
1398               clonedNode: function (node) {
1399                 return angular.copy(node);
1400               },
1401
1402               isDirty: function () {
1403                 return this.source.$parentNodesScope != this.parent ||
1404                   this.source.index() != this.index;
1405               },
1406
1407               isForeign: function () {
1408                 return this.source.$treeScope !== this.parent.$treeScope;
1409               },
1410
1411               eventArgs: function (elements, pos) {
1412                 return {
1413                   source: this.sourceInfo,
1414                   dest: {
1415                     index: this.index,
1416                     nodesScope: this.parent
1417                   },
1418                   elements: elements,
1419                   pos: pos
1420                 };
1421               },
1422
1423               apply: function () {
1424
1425                 var nodeData = this.source.$modelValue;
1426
1427                 // nodrop enabled on tree or parent
1428                 if (this.parent.nodropEnabled || this.parent.$treeScope.nodropEnabled) {
1429                   return;
1430                 }
1431
1432                 // node was dropped in the same place - do nothing
1433                 if (!this.isDirty()) {
1434                   return;
1435                 }
1436
1437                 // cloneEnabled and cross-tree so copy and do not remove from source
1438                 if (this.isClone() && this.isForeign()) {
1439                   this.parent.insertNode(this.index, this.sourceInfo.cloneModel);
1440                 } else { // Any other case, remove and reinsert
1441                   this.source.remove();
1442                   this.parent.insertNode(this.index, nodeData);
1443                 }
1444               }
1445             };
1446           },
1447
1448           /**
1449            * @ngdoc method
1450            * @name ui.tree#height
1451            * @methodOf ui.tree.service:UiTreeHelper
1452            *
1453            * @description
1454            * Get the height of an element.
1455            *
1456            * @param {Object} element Angular element.
1457            * @returns {String} Height
1458            */
1459           height: function (element) {
1460             return element.prop('scrollHeight');
1461           },
1462
1463           /**
1464            * @ngdoc method
1465            * @name ui.tree#width
1466            * @methodOf ui.tree.service:UiTreeHelper
1467            *
1468            * @description
1469            * Get the width of an element.
1470            *
1471            * @param {Object} element Angular element.
1472            * @returns {String} Width
1473            */
1474           width: function (element) {
1475             return element.prop('scrollWidth');
1476           },
1477
1478           /**
1479            * @ngdoc method
1480            * @name ui.tree#offset
1481            * @methodOf ui.nestedSortable.service:UiTreeHelper
1482            *
1483            * @description
1484            * Get the offset values of an element.
1485            *
1486            * @param {Object} element Angular element.
1487            * @returns {Object} Object with properties width, height, top and left
1488            */
1489           offset: function (element) {
1490             var boundingClientRect = element[0].getBoundingClientRect();
1491
1492             return {
1493               width: element.prop('offsetWidth'),
1494               height: element.prop('offsetHeight'),
1495               top: boundingClientRect.top + ($window.pageYOffset || $document[0].body.scrollTop || $document[0].documentElement.scrollTop),
1496               left: boundingClientRect.left + ($window.pageXOffset || $document[0].body.scrollLeft || $document[0].documentElement.scrollLeft)
1497             };
1498           },
1499
1500           /**
1501            * @ngdoc method
1502            * @name ui.tree#positionStarted
1503            * @methodOf ui.tree.service:UiTreeHelper
1504            *
1505            * @description
1506            * Get the start position of the target element according to the provided event properties.
1507            *
1508            * @param {Object} e Event
1509            * @param {Object} target Target element
1510            * @returns {Object} Object with properties offsetX, offsetY, startX, startY, nowX and dirX.
1511            */
1512           positionStarted: function (e, target) {
1513             var pos = {},
1514               pageX = e.pageX,
1515               pageY = e.pageY;
1516
1517             if (e.originalEvent && e.originalEvent.touches && (e.originalEvent.touches.length > 0)) {
1518               pageX = e.originalEvent.touches[0].pageX;
1519               pageY = e.originalEvent.touches[0].pageY;
1520             }
1521             pos.offsetX = pageX - this.offset(target).left;
1522             pos.offsetY = pageY - this.offset(target).top;
1523             pos.startX = pos.lastX = pageX;
1524             pos.startY = pos.lastY = pageY;
1525             pos.nowX = pos.nowY = pos.distX = pos.distY = pos.dirAx = 0;
1526             pos.dirX = pos.dirY = pos.lastDirX = pos.lastDirY = pos.distAxX = pos.distAxY = 0;
1527             return pos;
1528           },
1529
1530           positionMoved: function (e, pos, firstMoving) {
1531             var pageX = e.pageX,
1532               pageY = e.pageY,
1533               newAx;
1534             if (e.originalEvent && e.originalEvent.touches && (e.originalEvent.touches.length > 0)) {
1535               pageX = e.originalEvent.touches[0].pageX;
1536               pageY = e.originalEvent.touches[0].pageY;
1537             }
1538             // mouse position last events
1539             pos.lastX = pos.nowX;
1540             pos.lastY = pos.nowY;
1541
1542             // mouse position this events
1543             pos.nowX = pageX;
1544             pos.nowY = pageY;
1545
1546             // distance mouse moved between events
1547             pos.distX = pos.nowX - pos.lastX;
1548             pos.distY = pos.nowY - pos.lastY;
1549
1550             // direction mouse was moving
1551             pos.lastDirX = pos.dirX;
1552             pos.lastDirY = pos.dirY;
1553
1554             // direction mouse is now moving (on both axis)
1555             pos.dirX = pos.distX === 0 ? 0 : pos.distX > 0 ? 1 : -1;
1556             pos.dirY = pos.distY === 0 ? 0 : pos.distY > 0 ? 1 : -1;
1557
1558             // axis mouse is now moving on
1559             newAx = Math.abs(pos.distX) > Math.abs(pos.distY) ? 1 : 0;
1560
1561             // do nothing on first move
1562             if (firstMoving) {
1563               pos.dirAx = newAx;
1564               pos.moving = true;
1565               return;
1566             }
1567
1568             // calc distance moved on this axis (and direction)
1569             if (pos.dirAx !== newAx) {
1570               pos.distAxX = 0;
1571               pos.distAxY = 0;
1572             } else {
1573               pos.distAxX += Math.abs(pos.distX);
1574               if (pos.dirX !== 0 && pos.dirX !== pos.lastDirX) {
1575                 pos.distAxX = 0;
1576               }
1577
1578               pos.distAxY += Math.abs(pos.distY);
1579               if (pos.dirY !== 0 && pos.dirY !== pos.lastDirY) {
1580                 pos.distAxY = 0;
1581               }
1582             }
1583
1584             pos.dirAx = newAx;
1585           },
1586
1587           elementIsTreeNode: function (element) {
1588             return typeof element.attr('ui-tree-node') !== 'undefined';
1589           },
1590
1591           elementIsTreeNodeHandle: function (element) {
1592             return typeof element.attr('ui-tree-handle') !== 'undefined';
1593           },
1594           elementIsTree: function (element) {
1595             return typeof element.attr('ui-tree') !== 'undefined';
1596           },
1597           elementIsTreeNodes: function (element) {
1598             return typeof element.attr('ui-tree-nodes') !== 'undefined';
1599           },
1600           elementIsPlaceholder: function (element) {
1601             return element.hasClass(treeConfig.placeholderClass);
1602           },
1603           elementContainsTreeNodeHandler: function (element) {
1604             return element[0].querySelectorAll('[ui-tree-handle]').length >= 1;
1605           },
1606           treeNodeHandlerContainerOfElement: function (element) {
1607             return findFirstParentElementWithAttribute('ui-tree-handle', element[0]);
1608           }
1609         };
1610       }
1611     ]);
1612
1613   // TODO: optimize this loop
1614   function findFirstParentElementWithAttribute(attributeName, childObj) {
1615     // undefined if the mouse leaves the browser window
1616     if (childObj === undefined) {
1617       return null;
1618     }
1619     var testObj = childObj.parentNode,
1620       count = 1,
1621       // check for setAttribute due to exception thrown by Firefox when a node is dragged outside the browser window
1622       res = (typeof testObj.setAttribute === 'function' && testObj.hasAttribute(attributeName)) ? testObj : null;
1623     while (testObj && typeof testObj.setAttribute === 'function' && !testObj.hasAttribute(attributeName)) {
1624       testObj = testObj.parentNode;
1625       res = testObj;
1626       if (testObj === document.documentElement) {
1627         res = null;
1628         break;
1629       }
1630       count++;
1631     }
1632
1633     return res;
1634   }
1635
1636 })();