2 * @license Angular UI Tree v2.17.0
\r
3 * (c) 2010-2016. https://github.com/angular-ui-tree/angular-ui-tree
\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
21 defaultCollapsed: false
\r
29 angular.module('ui.tree')
\r
31 .controller('TreeHandleController', ['$scope', '$element',
\r
32 function ($scope, $element) {
\r
33 this.scope = $scope;
\r
35 $scope.$element = $element;
\r
36 $scope.$nodeScope = null;
\r
37 $scope.$type = 'uiTreeHandle';
\r
46 angular.module('ui.tree')
\r
47 .controller('TreeNodeController', ['$scope', '$element',
\r
48 function ($scope, $element) {
\r
49 this.scope = $scope;
\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
63 $scope.init = function (controllersArr) {
\r
64 var treeNodesCtrl = controllersArr[0];
\r
65 $scope.$treeScope = controllersArr[1] ? controllersArr[1].scope : null;
\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
74 $element.on('$destroy', function () {
\r
75 treeNodesCtrl.scope.destroySubNode($scope); // destroy sub nodes
\r
79 $scope.index = function () {
\r
80 return $scope.$parentNodesScope.$modelValue.indexOf($scope.$modelValue);
\r
83 $scope.dragEnabled = function () {
\r
84 return !($scope.$treeScope && !$scope.$treeScope.dragEnabled);
\r
87 $scope.isSibling = function (targetNode) {
\r
88 return $scope.$parentNodesScope == targetNode.$parentNodesScope;
\r
91 $scope.isChild = function (targetNode) {
\r
92 var nodes = $scope.childNodes();
\r
93 return nodes && nodes.indexOf(targetNode) > -1;
\r
96 $scope.prev = function () {
\r
97 var index = $scope.index();
\r
99 return $scope.siblings()[index - 1];
\r
104 $scope.siblings = function () {
\r
105 return $scope.$parentNodesScope.childNodes();
\r
108 $scope.childNodesCount = function () {
\r
109 return $scope.childNodes() ? $scope.childNodes().length : 0;
\r
112 $scope.hasChild = function () {
\r
113 return $scope.childNodesCount() > 0;
\r
116 $scope.childNodes = function () {
\r
117 return $scope.$childNodesScope && $scope.$childNodesScope.$modelValue ?
\r
118 $scope.$childNodesScope.childNodes() :
\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
128 $scope.remove = function () {
\r
129 return $scope.$parentNodesScope.removeNode($scope);
\r
132 $scope.toggle = function () {
\r
133 $scope.collapsed = !$scope.collapsed;
\r
134 $scope.$treeScope.$callbacks.toggle($scope.collapsed, $scope);
\r
137 $scope.collapse = function () {
\r
138 $scope.collapsed = true;
\r
141 $scope.expand = function () {
\r
142 $scope.collapsed = false;
\r
145 $scope.depth = function () {
\r
146 var parentNode = $scope.$parentNodeScope;
\r
148 return parentNode.depth() + 1;
\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
159 function countSubTreeDepth(scope) {
\r
160 var thisLevelDepth = 0,
\r
161 childNodes = scope.childNodes(),
\r
165 if (!childNodes || childNodes.length === 0) {
\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
173 return thisLevelDepth;
\r
176 $scope.maxSubDepth = function () {
\r
177 return $scope.$childNodesScope ? countSubTreeDepth($scope.$childNodesScope) : 0;
\r
186 angular.module('ui.tree')
\r
188 .controller('TreeNodesController', ['$scope', '$element',
\r
189 function ($scope, $element) {
\r
190 this.scope = $scope;
\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
199 $scope.nodropEnabled = false;
\r
200 $scope.maxDepth = 0;
\r
201 $scope.cloneEnabled = false;
\r
203 $scope.initSubNode = function (subNode) {
\r
204 if (!subNode.$modelValue) {
\r
207 $scope.$nodesMap[subNode.$modelValue.$$hashKey] = subNode;
\r
210 $scope.destroySubNode = function (subNode) {
\r
211 if (!subNode.$modelValue) {
\r
214 $scope.$nodesMap[subNode.$modelValue.$$hashKey] = null;
\r
217 $scope.accept = function (sourceNode, destIndex) {
\r
218 return $scope.$treeScope.$callbacks.accept(sourceNode, $scope, destIndex);
\r
221 $scope.beforeDrag = function (sourceNode) {
\r
222 return $scope.$treeScope.$callbacks.beforeDrag(sourceNode);
\r
225 $scope.isParent = function (node) {
\r
226 return node.$parentNodesScope == $scope;
\r
229 $scope.hasChild = function () {
\r
230 return $scope.$modelValue.length > 0;
\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
244 $scope.removeNode = function (node) {
\r
245 var index = $scope.$modelValue.indexOf(node.$modelValue);
\r
247 $scope.safeApply(function () {
\r
248 $scope.$modelValue.splice(index, 1)[0];
\r
250 return $scope.$treeScope.$callbacks.removed(node);
\r
255 $scope.insertNode = function (index, nodeData) {
\r
256 $scope.safeApply(function () {
\r
257 $scope.$modelValue.splice(index, 0, nodeData);
\r
261 $scope.childNodes = function () {
\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
271 $scope.depth = function () {
\r
272 if ($scope.$nodeScope) {
\r
273 return $scope.$nodeScope.depth();
\r
275 return 0; // if it has no $nodeScope, it's root
\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
294 angular.module('ui.tree')
\r
296 .controller('TreeController', ['$scope', '$element',
\r
297 function ($scope, $element) {
\r
298 this.scope = $scope;
\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
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
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
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
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
330 $scope.$emptyElm.remove();
\r
334 $scope.resetEmptyElement = this.resetEmptyElement;
\r
342 angular.module('ui.tree')
\r
343 .directive('uiTree', ['treeConfig', '$window',
\r
344 function (treeConfig, $window) {
\r
348 controller: 'TreeController',
\r
349 link: function (scope, element, attrs, ctrl) {
\r
359 angular.extend(config, treeConfig);
\r
360 if (config.treeClass) {
\r
361 element.addClass(config.treeClass);
\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
371 // If not, by setting a huge colspan we make sure it takes full width.
\r
372 emptyElmColspan = 1000000;
\r
374 tdElm = angular.element($window.document.createElement('td'))
\r
375 .attr('colspan', emptyElmColspan);
\r
376 scope.$emptyElm.append(tdElm);
\r
378 scope.$emptyElm = angular.element($window.document.createElement('div'));
\r
381 if (config.emptyTreeClass) {
\r
382 scope.$emptyElm.addClass(config.emptyTreeClass);
\r
385 scope.$watch('$nodesScope.$modelValue.length', function (val) {
\r
386 if (!angular.isNumber(val)) {
\r
390 ctrl.resetEmptyElement();
\r
393 scope.$watch(attrs.dragEnabled, function (val) {
\r
394 if ((typeof val) == 'boolean') {
\r
395 scope.dragEnabled = val;
\r
399 scope.$watch(attrs.emptyPlaceholderEnabled, function (val) {
\r
400 if ((typeof val) == 'boolean') {
\r
401 scope.emptyPlaceholderEnabled = val;
\r
402 ctrl.resetEmptyElement();
\r
406 scope.$watch(attrs.nodropEnabled, function (val) {
\r
407 if ((typeof val) == 'boolean') {
\r
408 scope.nodropEnabled = val;
\r
412 scope.$watch(attrs.cloneEnabled, function (val) {
\r
413 if ((typeof val) == 'boolean') {
\r
414 scope.cloneEnabled = val;
\r
418 scope.$watch(attrs.maxDepth, function (val) {
\r
419 if ((typeof val) == 'number') {
\r
420 scope.maxDepth = val;
\r
424 scope.$watch(attrs.dragDelay, function (val) {
\r
425 if ((typeof val) == 'number') {
\r
426 scope.dragDelay = val;
\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
442 callbacks.accept = function (sourceNodeScope, destNodesScope, destIndex) {
\r
443 return !(destNodesScope.nodropEnabled || destNodesScope.$treeScope.nodropEnabled || destNodesScope.outOfDepth(sourceNodeScope));
\r
446 callbacks.beforeDrag = function (sourceNodeScope) {
\r
450 callbacks.expandTimeoutStart = function()
\r
455 callbacks.expandTimeoutCancel = function()
\r
460 callbacks.expandTimeoutEnd = function()
\r
465 callbacks.removed = function (node) {
\r
470 * Callback is fired when a node is successfully dropped in a new location
\r
473 callbacks.dropped = function (event) {
\r
478 * Callback is fired each time the user starts dragging a node
\r
481 callbacks.dragStart = function (event) {
\r
486 * Callback is fired each time a dragged node is moved with the mouse/touch.
\r
489 callbacks.dragMove = function (event) {
\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
498 callbacks.dragStop = function (event) {
\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
508 * @returns {Boolean|Promise} Truthy (or rejected Promise) to cancel node move; falsy (or resolved promise)
\r
510 callbacks.beforeDrop = function (event) {
\r
515 * Callback is fired when a user toggles node (but after processing the toggle action)
\r
516 * @param sourceNodeScope
\r
519 callbacks.toggle = function (collapsed, sourceNodeScope) {
\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
532 scope.$callbacks = callbacks;
\r
545 angular.module('ui.tree')
\r
546 .directive('uiTreeHandle', ['treeConfig',
\r
547 function (treeConfig) {
\r
549 require: '^uiTreeNode',
\r
552 controller: 'TreeHandleController',
\r
553 link: function (scope, element, attrs, treeNodeCtrl) {
\r
555 angular.extend(config, treeConfig);
\r
556 if (config.handleClass) {
\r
557 element.addClass(config.handleClass);
\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
573 angular.module('ui.tree')
\r
575 .directive('uiTreeNode', ['treeConfig', 'UiTreeHelper', '$window', '$document', '$timeout', '$q',
\r
576 function (treeConfig, UiTreeHelper, $window, $document, $timeout, $q) {
\r
578 require: ['^uiTreeNodes', '^uiTree'],
\r
580 controller: 'TreeNodeController',
\r
581 link: function (scope, element, attrs, controllersArr) {
\r
582 // todo startPos is unused
\r
584 hasTouch = 'ontouchstart' in window,
\r
585 startPos, firstMoving, dragInfo, pos,
\r
586 placeElm, hiddenPlaceElm, dragElm,
\r
588 elements, // As a parameter for callbacks
\r
589 dragDelaying = true,
\r
590 dragStarted = false,
\r
592 body = document.body,
\r
593 html = document.documentElement,
\r
605 bindDragStartEvents,
\r
606 bindDragMoveEvents,
\r
607 unbindDragMoveEvents,
\r
613 angular.extend(config, treeConfig);
\r
614 if (config.nodeClass) {
\r
615 element.addClass(config.nodeClass);
\r
617 scope.init(controllersArr);
\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
623 scope.$watch(attrs.collapsed, function (val) {
\r
624 if ((typeof val) == 'boolean') {
\r
625 scope.collapsed = val;
\r
629 scope.$watch('collapsed', function (val) {
\r
630 UiTreeHelper.setNodeAttribute(scope, 'collapsed', val);
\r
631 attrs.$set('collapsed', val);
\r
634 scope.$watch(attrs.expandOnHover, function(val) {
\r
635 if ((typeof val) == 'boolean') {
\r
636 scope.expandOnHover = val;
\r
640 scope.$watch('expandOnHover', function (val) {
\r
641 UiTreeHelper.setNodeAttribute(scope, 'expandOnHover', val);
\r
642 attrs.$set('expandOnHover', val);
\r
645 scope.$on('angular-ui-tree:collapse-all', function () {
\r
646 scope.collapsed = true;
\r
649 scope.$on('angular-ui-tree:expand-all', function () {
\r
650 scope.collapsed = false;
\r
654 * Called when the user has grabbed a node and started dragging it
\r
657 dragStart = function (e) {
\r
658 // disable right click
\r
659 if (!hasTouch && (e.button === 2 || e.which === 3)) {
\r
663 // event has already fired in other scope
\r
664 if (e.uiTreeDragging || (e.originalEvent && e.originalEvent.uiTreeDragging)) {
\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
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
682 cloneElm = element.clone();
\r
683 isTreeNode = UiTreeHelper.elementIsTreeNode(eventElm);
\r
684 isTreeNodeHandle = UiTreeHelper.elementIsTreeNodeHandle(eventElm);
\r
686 if (!isTreeNode && !isTreeNodeHandle) {
\r
690 if (isTreeNode && UiTreeHelper.elementContainsTreeNodeHandler(eventElm)) {
\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
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
711 if (!scope.beforeDrag(scope)) {
\r
715 e.uiTreeDragging = true; // stop event bubbling
\r
716 if (e.originalEvent) {
\r
717 e.originalEvent.uiTreeDragging = true;
\r
719 e.preventDefault();
\r
720 eventObj = UiTreeHelper.eventObj(e);
\r
722 firstMoving = true;
\r
723 dragInfo = UiTreeHelper.dragInfo(scope);
\r
725 tagName = element.prop('tagName');
\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
734 placeElm = angular.element($window.document.createElement(tagName))
\r
735 .addClass(config.placeholderClass);
\r
737 hiddenPlaceElm = angular.element($window.document.createElement(tagName));
\r
738 if (config.hiddenClass) {
\r
739 hiddenPlaceElm.addClass(config.hiddenClass);
\r
742 pos = UiTreeHelper.positionStarted(eventObj, element);
\r
743 placeElm.css('height', UiTreeHelper.height(element) + 'px');
\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
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
753 document.body.setAttribute('ui-tree-cursor', $document.find('body').css('cursor') || '');
\r
754 $document.find('body').css({'cursor': hStyle.cursor + '!important'});
\r
757 if (scope.sourceOnly) {
\r
758 placeElm.css('display', 'none');
\r
760 element.after(placeElm);
\r
761 element.after(hiddenPlaceElm);
\r
762 if (dragInfo.isClone() && scope.sourceOnly) {
\r
763 dragElm.append(cloneElm);
\r
765 dragElm.append(element);
\r
768 $document.find('body').append(dragElm);
\r
771 'left': eventObj.pageX - pos.offsetX + 'px',
\r
772 'top': eventObj.pageY - pos.offsetY + 'px'
\r
775 placeholder: placeElm,
\r
779 bindDragMoveEvents();
\r
780 // Fire dragStart callback
\r
781 scope.$apply(function () {
\r
782 scope.$treeScope.$callbacks.dragStart(dragInfo.eventArgs(elements, pos));
\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
789 dragMove = function (e) {
\r
790 var eventObj = UiTreeHelper.eventObj(e),
\r
810 e.preventDefault();
\r
812 if ($window.getSelection) {
\r
813 $window.getSelection().removeAllRanges();
\r
814 } else if ($window.document.selection) {
\r
815 $window.document.selection.empty();
\r
818 leftElmPos = eventObj.pageX - pos.offsetX;
\r
819 topElmPos = eventObj.pageY - pos.offsetY;
\r
821 //dragElm can't leave the screen on the left
\r
822 if (leftElmPos < 0) {
\r
826 //dragElm can't leave the screen on the top
\r
827 if (topElmPos < 0) {
\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
836 //dragElm can't leave the screen on the right
\r
837 if ((leftElmPos + 10) > document_width) {
\r
838 leftElmPos = document_width - 10;
\r
842 'left': leftElmPos + 'px',
\r
843 'top': topElmPos + 'px'
\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
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
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
860 UiTreeHelper.positionMoved(e, pos, firstMoving);
\r
862 firstMoving = false;
\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
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
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
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
886 displayElm = dragElm[0].style.display;
\r
887 dragElm[0].style.display = 'none';
\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
894 targetElm = angular.element($window.document.elementFromPoint(targetX, targetY));
\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
903 if (angular.isFunction(dragElm.show)) {
\r
906 dragElm[0].style.display = displayElm;
\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
915 // Detect out of bounds condition, update drop target display, and prevent drop
\r
918 // Remove the placeholder
\r
921 // If the target was an empty tree, replace the empty element placeholder
\r
923 treeScope.resetEmptyElement();
\r
929 if (pos.dirAx && pos.distAxX >= config.levelThreshold) {
\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
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
947 target = dragInfo.parentNode(); // As a sibling of it's parent node
\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
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
974 // check it's new position
\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
985 if (targetNode.$type == 'uiTree' && targetNode.dragEnabled) {
\r
986 isEmpty = targetNode.isEmpty(); // Check if it's empty tree
\r
989 if (targetNode.$type == 'uiTreeHandle') {
\r
990 targetNode = targetNode.$nodeScope;
\r
993 if (targetNode.$type != 'uiTreeNode'
\r
994 && !isEmpty) { // Check if it is a uiTreeNode or it's an empty tree
\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
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
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
1016 scope.$callbacks.expandTimeoutCancel();
\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
1026 scope.$callbacks.expandTimeoutStart();
\r
1027 scope.expandTimeout = $timeout(function()
\r
1029 scope.$callbacks.expandTimeoutEnd();
\r
1030 targetNode.collapsed = false;
\r
1031 }, scope.expandOnHover);
\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
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
1046 targetElm.after(placeElm);
\r
1047 dragInfo.moveTo(targetNode.$parentNodesScope, targetNode.siblings(), targetNode.index() + 1);
\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
1053 outOfBounds = true;
\r
1058 scope.$apply(function () {
\r
1059 scope.$treeScope.$callbacks.dragMove(dragInfo.eventArgs(elements, pos));
\r
1064 dragEnd = function (e) {
\r
1065 var dragEventArgs = dragInfo.eventArgs(elements, pos);
\r
1066 e.preventDefault();
\r
1067 unbindDragMoveEvents();
\r
1069 $timeout.cancel(scope.expandTimeout);
\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
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
1083 // promise rejected - revert the node to its original position
\r
1084 .catch(function () {
\r
1085 bindDragStartEvents();
\r
1087 .finally(function () {
\r
1088 hiddenPlaceElm.replaceWith(scope.$element);
\r
1089 placeElm.remove();
\r
1091 if (dragElm) { // drag element is attached to the mouse pointer
\r
1095 scope.$treeScope.$callbacks.dragStop(dragEventArgs);
\r
1096 scope.$$allowNodeDrop = false;
\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
1109 dragStartEvent = function (e) {
\r
1110 if (scope.dragEnabled()) {
\r
1115 dragMoveEvent = function (e) {
\r
1119 dragEndEvent = function (e) {
\r
1120 scope.$$allowNodeDrop = true;
\r
1124 dragCancelEvent = function (e) {
\r
1128 dragDelay = (function () {
\r
1132 exec: function (fn, ms) {
\r
1137 to = $timeout(fn, ms);
\r
1139 cancel: function () {
\r
1140 $timeout.cancel(to);
\r
1146 * Binds the mouse/touch events to enable drag start for this node
\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
1154 element.bind('touchend touchcancel mouseup', function () {
\r
1155 dragDelay.cancel();
\r
1158 bindDragStartEvents();
\r
1161 * Binds mouse/touch events that handle moving/dropping this dragged node
\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
1173 * Unbinds mouse/touch events that handle moving/dropping this dragged node
\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
1184 keydownHandler = function (e) {
\r
1185 if (e.keyCode == 27) {
\r
1186 scope.$$allowNodeDrop = false;
\r
1191 angular.element($window.document).bind('keydown', keydownHandler);
\r
1193 //unbind handler that retains scope
\r
1194 scope.$on('$destroy', function () {
\r
1195 angular.element($window.document).unbind('keydown', keydownHandler);
\r
1207 angular.module('ui.tree')
\r
1208 .directive('uiTreeNodes', ['treeConfig', '$window',
\r
1209 function (treeConfig) {
\r
1211 require: ['ngModel', '?^uiTreeNode', '^uiTree'],
\r
1214 controller: 'TreeNodesController',
\r
1215 link: function (scope, element, attrs, controllersArr) {
\r
1218 ngModel = controllersArr[0],
\r
1219 treeNodeCtrl = controllersArr[1],
\r
1220 treeCtrl = controllersArr[2];
\r
1222 angular.extend(config, treeConfig);
\r
1223 if (config.nodesClass) {
\r
1224 element.addClass(config.nodesClass);
\r
1227 if (treeNodeCtrl) {
\r
1228 treeNodeCtrl.scope.$childNodesScope = scope;
\r
1229 scope.$nodeScope = treeNodeCtrl.scope;
\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
1234 scope.$treeScope = treeCtrl.scope;
\r
1237 ngModel.$render = function () {
\r
1238 scope.$modelValue = ngModel.$modelValue;
\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
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
1258 attrs.$observe('horizontal', function (val) {
\r
1259 scope.horizontal = ((typeof val) != 'undefined');
\r
1271 angular.module('ui.tree')
\r
1275 * @name ui.tree.service:UiTreeHelper
\r
1276 * @requires ng.$document
\r
1277 * @requires ng.$window
\r
1280 * angular-ui-tree.
\r
1282 .factory('UiTreeHelper', ['$document', '$window', 'treeConfig',
\r
1283 function ($document, $window, treeConfig) {
\r
1287 * A hashtable used to storage data of nodes
\r
1292 setNodeAttribute: function (scope, attrName, val) {
\r
1293 if (!scope.$modelValue) {
\r
1296 var data = this.nodesData[scope.$modelValue.$$hashKey];
\r
1299 this.nodesData[scope.$modelValue.$$hashKey] = data;
\r
1301 data[attrName] = val;
\r
1304 getNodeAttribute: function (scope, attrName) {
\r
1305 if (!scope.$modelValue) {
\r
1308 var data = this.nodesData[scope.$modelValue.$$hashKey];
\r
1310 return data[attrName];
\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
1321 nodrag: function (targetElm) {
\r
1322 if (typeof targetElm.attr('data-nodrag') != 'undefined') {
\r
1323 return targetElm.attr('data-nodrag') !== 'false';
\r
1329 * get the event object for touches
\r
1330 * @param {[type]} e [description]
\r
1331 * @return {[type]} [description]
\r
1333 eventObj: function (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
1343 dragInfo: function (node) {
\r
1347 cloneModel: node.$treeScope.cloneEnabled === true ? angular.copy(node.$modelValue) : undefined,
\r
1349 index: node.index(),
\r
1350 nodesScope: node.$parentNodesScope
\r
1352 index: node.index(),
\r
1353 siblings: node.siblings().slice(0),
\r
1354 parent: node.$parentNodesScope,
\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
1361 // If source node is in the target nodes
\r
1362 var i = this.siblings.indexOf(this.source);
\r
1364 this.siblings.splice(i, 1);
\r
1365 if (this.source.index() < index) {
\r
1370 this.siblings.splice(index, 0, this.source);
\r
1371 this.index = index;
\r
1374 parentNode: function () {
\r
1375 return this.parent.$nodeScope;
\r
1378 prev: function () {
\r
1379 if (this.index > 0) {
\r
1380 return this.siblings[this.index - 1];
\r
1386 next: function () {
\r
1387 if (this.index < this.siblings.length - 1) {
\r
1388 return this.siblings[this.index + 1];
\r
1394 isClone: function () {
\r
1395 return this.source.$treeScope.cloneEnabled === true;
\r
1398 clonedNode: function (node) {
\r
1399 return angular.copy(node);
\r
1402 isDirty: function () {
\r
1403 return this.source.$parentNodesScope != this.parent ||
\r
1404 this.source.index() != this.index;
\r
1407 isForeign: function () {
\r
1408 return this.source.$treeScope !== this.parent.$treeScope;
\r
1411 eventArgs: function (elements, pos) {
\r
1413 source: this.sourceInfo,
\r
1415 index: this.index,
\r
1416 nodesScope: this.parent
\r
1418 elements: elements,
\r
1423 apply: function () {
\r
1425 var nodeData = this.source.$modelValue;
\r
1427 // nodrop enabled on tree or parent
\r
1428 if (this.parent.nodropEnabled || this.parent.$treeScope.nodropEnabled) {
\r
1432 // node was dropped in the same place - do nothing
\r
1433 if (!this.isDirty()) {
\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
1450 * @name ui.tree#height
\r
1451 * @methodOf ui.tree.service:UiTreeHelper
\r
1454 * Get the height of an element.
\r
1456 * @param {Object} element Angular element.
\r
1457 * @returns {String} Height
\r
1459 height: function (element) {
\r
1460 return element.prop('scrollHeight');
\r
1465 * @name ui.tree#width
\r
1466 * @methodOf ui.tree.service:UiTreeHelper
\r
1469 * Get the width of an element.
\r
1471 * @param {Object} element Angular element.
\r
1472 * @returns {String} Width
\r
1474 width: function (element) {
\r
1475 return element.prop('scrollWidth');
\r
1480 * @name ui.tree#offset
\r
1481 * @methodOf ui.nestedSortable.service:UiTreeHelper
\r
1484 * Get the offset values of an element.
\r
1486 * @param {Object} element Angular element.
\r
1487 * @returns {Object} Object with properties width, height, top and left
\r
1489 offset: function (element) {
\r
1490 var boundingClientRect = element[0].getBoundingClientRect();
\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
1502 * @name ui.tree#positionStarted
\r
1503 * @methodOf ui.tree.service:UiTreeHelper
\r
1506 * Get the start position of the target element according to the provided event properties.
\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
1512 positionStarted: function (e, target) {
\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
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
1530 positionMoved: function (e, pos, firstMoving) {
\r
1531 var pageX = e.pageX,
\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
1538 // mouse position last events
\r
1539 pos.lastX = pos.nowX;
\r
1540 pos.lastY = pos.nowY;
\r
1542 // mouse position this events
\r
1546 // distance mouse moved between events
\r
1547 pos.distX = pos.nowX - pos.lastX;
\r
1548 pos.distY = pos.nowY - pos.lastY;
\r
1550 // direction mouse was moving
\r
1551 pos.lastDirX = pos.dirX;
\r
1552 pos.lastDirY = pos.dirY;
\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
1558 // axis mouse is now moving on
\r
1559 newAx = Math.abs(pos.distX) > Math.abs(pos.distY) ? 1 : 0;
\r
1561 // do nothing on first move
\r
1562 if (firstMoving) {
\r
1563 pos.dirAx = newAx;
\r
1564 pos.moving = true;
\r
1568 // calc distance moved on this axis (and direction)
\r
1569 if (pos.dirAx !== newAx) {
\r
1573 pos.distAxX += Math.abs(pos.distX);
\r
1574 if (pos.dirX !== 0 && pos.dirX !== pos.lastDirX) {
\r
1578 pos.distAxY += Math.abs(pos.distY);
\r
1579 if (pos.dirY !== 0 && pos.dirY !== pos.lastDirY) {
\r
1584 pos.dirAx = newAx;
\r
1587 elementIsTreeNode: function (element) {
\r
1588 return typeof element.attr('ui-tree-node') !== 'undefined';
\r
1591 elementIsTreeNodeHandle: function (element) {
\r
1592 return typeof element.attr('ui-tree-handle') !== 'undefined';
\r
1594 elementIsTree: function (element) {
\r
1595 return typeof element.attr('ui-tree') !== 'undefined';
\r
1597 elementIsTreeNodes: function (element) {
\r
1598 return typeof element.attr('ui-tree-nodes') !== 'undefined';
\r
1600 elementIsPlaceholder: function (element) {
\r
1601 return element.hasClass(treeConfig.placeholderClass);
\r
1603 elementContainsTreeNodeHandler: function (element) {
\r
1604 return element[0].querySelectorAll('[ui-tree-handle]').length >= 1;
\r
1606 treeNodeHandlerContainerOfElement: function (element) {
\r
1607 return findFirstParentElementWithAttribute('ui-tree-handle', element[0]);
\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
1619 var testObj = childObj.parentNode,
\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
1626 if (testObj === document.documentElement) {
\r