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