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