diff --git a/DOCUMENTS.md b/DOCUMENTS.md index d1ddd71..40cdff6 100644 --- a/DOCUMENTS.md +++ b/DOCUMENTS.md @@ -282,6 +282,9 @@ $scope.$callbacks = { beforeDrop: function (event) { return true; }, + loadChildren: function (node) { + // return array or promise resolved with children + }, calsIndent: function (level) { if (level - 1 < 1) { return $scope.indent_plus + ($scope.indent_unit ? $scope.indent_unit : 'px'); @@ -294,6 +297,30 @@ $scope.$callbacks = { } }; ``` + +* Lazy load children on expand: + * Mark nodes that have unloaded children with `node.__has_children__ = true` or `node.__lazy__ = true` so the expand icon is shown. + * Provide `callbacks.loadChildren` to return an array (sync) or a promise that resolves to an array (async). + * When `loadChildren` resolves, children are inserted into `node.__children__`, the node expands, and the tree is reloaded. + * Example: + ```js + $scope.tree_data = [{ + title: 'Root', + __lazy__: true, + __children__: [] + }]; + + $scope.callbacks = { + loadChildren: function (node) { + return $http.get('/api/tree/' + node.id + '/children').then(function (res) { + return res.data; + }); + } + }; + ``` + ```html + + ``` * Functions extended in control (attribute 'tree-control'): ```html diff --git a/dist/ng-tree-dnd.debug.js b/dist/ng-tree-dnd.debug.js index 83a59d1..22724f0 100644 --- a/dist/ng-tree-dnd.debug.js +++ b/dist/ng-tree-dnd.debug.js @@ -58,7 +58,8 @@ return { restrict: 'A', link: function (scope, element, attrs) { - scope.$watch( + // Store the deregistration function to prevent memory leaks + var unwatchCompile = scope.$watch( attrs.compile, function (new_val) { if (new_val) { if (angular.isFunction(element.empty)) { @@ -71,6 +72,14 @@ } } ); + + // Clean up watch on scope destroy + scope.$on('$destroy', function () { + if (unwatchCompile) { + unwatchCompile(); + unwatchCompile = null; + } + }); } }; }] @@ -81,18 +90,28 @@ return { restrict: 'A', link: function (scope, element, attrs) { - scope.$watch( + // Store the deregistration function to prevent memory leaks + var unwatchCompileReplace = scope.$watch( attrs.compileReplace, function (new_val) { if (new_val) { element.replaceWith($compile(new_val)(scope)); } } ); + + // Clean up watch on scope destroy + scope.$on('$destroy', function () { + if (unwatchCompileReplace) { + unwatchCompileReplace(); + unwatchCompileReplace = null; + } + }); } }; }] ); + angular.module('ntt.TreeDnD') .directive('treeDndNodeHandle', function () { return { restrict: 'A', scope: true, link: function (scope, element/*, attrs*/) { scope.$type = 'TreeDnDNodeHandle'; if (scope.$class.handle) { element.addClass(scope.$class.handle); } } }; }); angular.module('ntt.TreeDnD') @@ -181,10 +200,40 @@ angular.module('ntt.TreeDnD') objexpr = '[' + objprops.join(',') + ']'; - scope.$watch(objexpr, fnWatchNode, true); + var unwatchNode = scope.$watch(objexpr, fnWatchNode, true); scope.$on('$destroy', function () { + //removeIf(nodebug) + console.log('Destroyed Node'); + //endRemoveIf(nodebug) + + // Deregister the watch first + if (unwatchNode) { + unwatchNode(); + unwatchNode = null; + } + + // Remove from scope cache scope.deleteScope(scope, scope[keyNode]); + + // Remove from viewport + $TreeDnDViewport.remove(scope, element); + + // Clear the __inited__ flag on the node + if (scope[keyNode]) { + scope[keyNode].__inited__ = false; + } + + // Clear element reference to allow DOM garbage collection + scope.$element = null; + + // Clear cached child element reference + childsElem = null; + + // Clear function references that may hold closures + scope.getData = null; + scope.getElementChilds = null; + scope.getScopeNode = null; }); function fnWatchNode(newVal, oldVal, scope) { @@ -204,6 +253,7 @@ angular.module('ntt.TreeDnD') parentNode = scope.tree_nodes[parentReal] || null, _childs = nodeOf.__children__, _len = _childs.length, + _hasChilds = _len > 0 || nodeOf.__has_children__ === true || nodeOf.__lazy__ === true, _i; if (!nodeOf.__inited__) { @@ -227,7 +277,7 @@ angular.module('ntt.TreeDnD') nodeOf.__visible__ = true; } - if (_len === 0) { + if (!_hasChilds) { _icon = -1; } else { if (nodeOf.__expanded__) { @@ -268,6 +318,7 @@ angular.module('ntt.TreeDnD') }] ); + angular.module('ntt.TreeDnD') .directive('treeDndNodes', function () { return { @@ -291,11 +342,11 @@ angular.module('ntt.TreeDnD') 'treeDnd', fnInitTreeDnD); fnInitTreeDnD.$inject = [ - '$timeout', '$http', '$compile', '$parse', '$window', '$document', '$templateCache', + '$timeout', '$http', '$compile', '$parse', '$window', '$document', '$templateCache', '$q', '$TreeDnDTemplate', '$TreeDnDClass', '$TreeDnDHelper', '$TreeDnDPlugin', '$TreeDnDViewport' ]; -function fnInitTreeDnD($timeout, $http, $compile, $parse, $window, $document, $templateCache, +function fnInitTreeDnD($timeout, $http, $compile, $parse, $window, $document, $templateCache, $q, $TreeDnDTemplate, $TreeDnDClass, $TreeDnDHelper, $TreeDnDPlugin, $TreeDnDViewport) { return { restrict: 'E', @@ -410,8 +461,18 @@ function fnInitTreeDnD($timeout, $http, $compile, $parse, $window, $document, $t } if (passedExpand) { + if (node.__expanded__) { + node.__expanded__ = false; + return; + } + if (node.__children__.length > 0) { - node.__expanded__ = !node.__expanded__; + node.__expanded__ = true; + return; + } + + if (nodeHasChildren(node) && angular.isFunction($scope.$callbacks.loadChildren)) { + $scope.loadChildren(node); } } }; @@ -480,6 +541,9 @@ function fnInitTreeDnD($timeout, $http, $compile, $parse, $window, $document, $t this.for_all_descendants(_clone, this.changeKey); return _clone; }, + loadChildren: function () { + return null; + }, remove: function (node, parent, _this, delayReload) { var temp = parent.splice(node.__index__, 1)[0]; if (!delayReload) { @@ -490,6 +554,13 @@ function fnInitTreeDnD($timeout, $http, $compile, $parse, $window, $document, $t clearInfo: function (node) { delete node.__inited__; delete node.__visible__; + delete node.__icon__; + delete node.__icon_class__; + delete node.__level__; + delete node.__index__; + delete node.__index_real__; + delete node.__parent_real__; + delete node.__dept__; // always changed after call reload_data //delete node.__hashKey__; @@ -535,6 +606,38 @@ function fnInitTreeDnD($timeout, $http, $compile, $parse, $window, $document, $t return $scope; }; + function nodeHasChildren(node) { + return (angular.isArray(node.__children__) && node.__children__.length > 0) || + node.__has_children__ === true || + node.__lazy__ === true; + } + + $scope.loadChildren = function (node) { + if (!node || node.__loading__) { + return $q.when([]); + } + + node.__loading__ = true; + + return $q.when($scope.$callbacks.loadChildren(node)).then( + function (children) { + if (angular.isArray(children)) { + node.__children__ = children; + } else if (!angular.isArray(node.__children__)) { + node.__children__ = []; + } + + node.__lazy__ = false; + node.__has_children__ = node.__children__.length > 0; + node.__expanded__ = node.__children__.length > 0; + reload_data(); + return node.__children__; + } + ).finally(function () { + node.__loading__ = false; + }); + }; + if ($attrs.enableDrag || $attrs.enableDrop) { $scope.placeElm = null; // $scope.dragBorder = 30; @@ -813,6 +916,8 @@ function fnInitTreeDnD($timeout, $http, $compile, $parse, $window, $document, $t beginAnd: true }, tree, + // Array to store watch deregistration functions for cleanup + _watchDeregistrations = [], _watches = [ [ 'enableDrag', @@ -986,7 +1091,8 @@ function fnInitTreeDnD($timeout, $http, $compile, $parse, $window, $document, $t } if ($attrs.treeData) { - $scope.$watch( + // Store deregistration function for treeData watch + var unwatchTreeData = $scope.$watch( $attrs.treeData, function (val) { if (angular.equals(val, $scope.treeData)) { return; @@ -998,8 +1104,70 @@ function fnInitTreeDnD($timeout, $http, $compile, $parse, $window, $document, $t } }, true ); + _watchDeregistrations.push(unwatchTreeData); } + $scope.$on('$destroy', function () { + // Cancel any pending timeouts + if (timeReloadData) { + $timeout.cancel(timeReloadData); + timeReloadData = null; + } + tmpTreeData = null; + + // Deregister all watches to prevent memory leaks + var i, len; + for (i = 0, len = _watchDeregistrations.length; i < len; i++) { + if (_watchDeregistrations[i]) { + _watchDeregistrations[i](); + } + } + _watchDeregistrations.length = 0; + + // Clear all scope references in $globals + if ($scope.$globals) { + var keys = Object.keys($scope.$globals); + for (i = 0, len = keys.length; i < len; i++) { + delete $scope.$globals[keys[i]]; + } + $scope.$globals = null; + } + + // Remove and clean up placeholder element + if ($scope.placeElm) { + $scope.placeElm.remove(); + $scope.placeElm = null; + } + + // Remove and clean up status element + if ($scope.statusElm) { + $scope.statusElm.remove(); + $scope.statusElm = null; + } + + // Clear tree node references + if ($scope.tree_nodes) { + $scope.tree_nodes.length = 0; + $scope.tree_nodes = null; + } + + // Clear treeData references + if ($scope.treeData) { + $scope.treeData = null; + } + + // Clear callbacks to break circular references + $scope.$callbacks = null; + + // Clear column definitions + $scope.colDefinitions = null; + + // Clear tree control reference + if (tree) { + tree = null; + } + }); + function timeLoadData() { $scope.treeData = tmpTreeData; reload_data(); @@ -1057,7 +1225,8 @@ function fnInitTreeDnD($timeout, $http, $compile, $parse, $window, $document, $t return;//jmp } if (typeof $attrs[nameAttr] === 'string') { - $scope.$watch( + // Store deregistration function for cleanup + var unwatchFn = $scope.$watch( $attrs[nameAttr], function (val) { if (typeof type === 'string' && typeof val === type || angular.isArray(type) && type.indexOf(typeof val) > -1 @@ -1076,6 +1245,7 @@ function fnInitTreeDnD($timeout, $http, $compile, $parse, $window, $document, $t } }, true ); + _watchDeregistrations.push(unwatchFn); } else { if (angular.isFunction(fnNotExist)) { @@ -1156,11 +1326,13 @@ function fnInitTreeDnD($timeout, $http, $compile, $parse, $window, $document, $t node.__parent__ = parent; _len = node.__children__.length; - if (angular.isUndefinedOrNull(node.__expanded__) && _len > 0) { + var _hasChildren = _len > 0 || node.__has_children__ === true || node.__lazy__ === true; + + if (angular.isUndefinedOrNull(node.__expanded__) && _hasChildren) { node.__expanded__ = level < $scope.expandLevel; } - if (_len === 0) { + if (!_hasChildren) { _icon = -1; } else { if (node.__expanded__) { @@ -1214,9 +1386,18 @@ function fnInitTreeDnD($timeout, $http, $compile, $parse, $window, $document, $t function init_data(data) { - // clear memory - if (angular.isDefined($scope.tree_nodes)) { - delete $scope.tree_nodes; + // clear memory - properly clean up old nodes to prevent memory leaks + if (angular.isDefined($scope.tree_nodes) && $scope.tree_nodes) { + // Clear internal properties that may hold references + var i, len, node; + for (i = 0, len = $scope.tree_nodes.length; i < len; i++) { + node = $scope.tree_nodes[i]; + if (node) { + // Clear the __inited__ flag that creates circular references + delete node.__inited__; + } + } + $scope.tree_nodes.length = 0; } $scope.tree_nodes = data; @@ -1438,1916 +1619,2024 @@ function fnInitTreeDnD($timeout, $http, $compile, $parse, $window, $document, $t angular.module('ntt.TreeDnD') - .factory('$TreeDnDConvert', function () { - var _$initConvert = { - line2tree: function (data, primaryKey, parentKey, callback) { - callback = typeof callback === 'function' ? callback : function () { - }; - if (!data || data.length === 0 || !primaryKey || !parentKey) { - return []; + .factory('$TreeDnDDrag', [ + '$timeout', '$TreeDnDHelper', + function ($timeout, $TreeDnDHelper) { + function _fnPlaceHolder(e, $params) { + if ($params.placeElm) { + var _offset = $TreeDnDHelper.offset($params.placeElm); + if (_offset.top <= e.pageY && e.pageY <= _offset.top + _offset.height && + _offset.left <= e.pageX && e.pageX <= _offset.left + _offset.width + ) { + return true; + } } - var tree = [], - rootIds = [], - item = data[0], - _primary = item[primaryKey], - treeObjs = {}, - parentId, parent, - len = data.length, - i = 0; + return false; + } - while (i < len) { - item = data[i++]; - callback(item); - _primary = item[primaryKey]; - treeObjs[_primary] = item; + function _fnDragStart(e, $params) { + if (!$params.hasTouch && (e.button === 2 || e.which === 3)) { + // disable right click + return; } - i = 0; - while (i < len) { - item = data[i++]; - callback(item); - _primary = item[primaryKey]; - treeObjs[_primary] = item; - parentId = item[parentKey]; - if (parentId) { - parent = treeObjs[parentId]; - if (parent) { - if (parent.__children__) { - parent.__children__.push(item); - } else { - parent.__children__ = [item]; - } - } - } else { - rootIds.push(_primary); - } + + if (e.uiTreeDragging || e.originalEvent && e.originalEvent.uiTreeDragging) { // event has already fired in other scope. + return; } - len = rootIds.length; - for (i = 0; i < len; i++) { - tree.push(treeObjs[rootIds[i]]); + + // the element which is clicked. + var eventElm = angular.element(e.target), + eventScope = eventElm.scope(); + if (!eventScope || !eventScope.$type) { + return; } - return tree; - }, - tree2tree: function access_child(data, containKey, callback) { - callback = typeof callback === 'function' ? callback : function () { - }; - var _tree = [], - _i, - _len = data ? data.length : 0, - _copy, _child; - for (_i = 0; _i < _len; _i++) { - _copy = angular.copy(data[_i]); - callback(_copy); - if (angular.isArray(_copy[containKey]) && _copy[containKey].length > 0) { - _child = access_child(_copy[containKey], containKey, callback); - delete _copy[containKey]; - _copy.__children__ = _child; + // if (eventScope.$type !== 'TreeDnDNode') { // Check if it is a node or a handle + // return; + // } + + if (eventScope.$type !== 'TreeDnDNodeHandle') { // If the node has a handle, then it should be clicked by the handle + return; + } + + var eventElmTagName = eventElm.prop('tagName').toLowerCase(), + dragScope, + _$scope = $params.$scope; + if (eventElmTagName === 'input' + || eventElmTagName === 'textarea' + || eventElmTagName === 'button' + || eventElmTagName === 'select') { // if it's a input or button, ignore it + return; + } + // check if it or it's parents has a 'data-nodrag' attribute + while (eventElm && eventElm[0] && eventElm[0] !== $params.element) { + if ($TreeDnDHelper.nodrag(eventElm)) { // if the node mark as `nodrag`, DONOT drag it. + return; } - _tree.push(_copy); + eventElm = eventElm.parent(); } - return _tree; - } - }; - return _$initConvert; - }); + e.uiTreeDragging = true; // stop event bubbling + if (e.originalEvent) { + e.originalEvent.uiTreeDragging = true; + } + e.preventDefault(); -angular.module('ntt.TreeDnD') - .factory('$TreeDnDHelper', [ - '$document', '$window', - function ($document, $window) { - var _$helper = { - nodrag: function (targetElm) { - return typeof targetElm.attr('data-nodrag') !== 'undefined'; - }, - eventObj: function (e) { - var obj = e; - if (e.targetTouches !== undefined) { - obj = e.targetTouches.item(0); - } else if (e.originalEvent !== undefined && e.originalEvent.targetTouches !== undefined) { - obj = e.originalEvent.targetTouches.item(0); - } - return obj; - }, - dragInfo: function (scope) { - var _node = scope.getData(), - _tree = scope.getScopeTree(), - _parent = scope.getNode(_node.__parent_real__); + dragScope = eventScope.getScopeNode(); - return { - node: _node, - parent: _parent, - move: { - parent: _parent, - pos: _node.__index__ - }, - scope: scope, - target: _tree, - drag: _tree, - drop: scope.getPrevSibling(_node), - changed: false - }; - }, - height: function (element) { - return element.prop('scrollHeight'); - }, - width: function (element) { - return element.prop('scrollWidth'); - }, - offset: function (element) { - var boundingClientRect = element[0].getBoundingClientRect(); - return { - width: element.prop('offsetWidth'), - height: element.prop('offsetHeight'), - top: boundingClientRect.top + ($window.pageYOffset || $document[0].body.scrollTop || $document[0].documentElement.scrollTop), - left: boundingClientRect.left + ($window.pageXOffset || $document[0].body.scrollLeft || $document[0].documentElement.scrollLeft) - }; - }, - positionStarted: function (e, target) { - return { - offsetX: e.pageX - this.offset(target).left, - offsetY: e.pageY - this.offset(target).top, - startX: e.pageX, - lastX: e.pageX, - startY: e.pageY, - lastY: e.pageY, - nowX: 0, - nowY: 0, - distX: 0, - distY: 0, - dirAx: 0, - dirX: 0, - dirY: 0, - lastDirX: 0, - lastDirY: 0, - distAxX: 0, - distAxY: 0 - }; - }, - positionMoved: function (e, pos, firstMoving) { - // mouse position last events - pos.lastX = pos.nowX; - pos.lastY = pos.nowY; + $params.dragInfo = $TreeDnDHelper.dragInfo(dragScope); - // mouse position this events - pos.nowX = e.pageX; - pos.nowY = e.pageY; - - // distance mouse moved between events - pos.distX = pos.nowX - pos.lastX; - pos.distY = pos.nowY - pos.lastY; - - // direction mouse was moving - pos.lastDirX = pos.dirX; - pos.lastDirY = pos.dirY; - - // direction mouse is now moving (on both axis) - pos.dirX = pos.distX === 0 ? 0 : pos.distX > 0 ? 1 : -1; - pos.dirY = pos.distY === 0 ? 0 : pos.distY > 0 ? 1 : -1; - - // axis mouse is now moving on - var newAx = Math.abs(pos.distX) > Math.abs(pos.distY) ? 1 : 0; - - // do nothing on first move - if (firstMoving) { - pos.dirAx = newAx; - pos.moving = true; - return; - } - - // calc distance moved on this axis (and direction) - if (pos.dirAx !== newAx) { - pos.distAxX = 0; - pos.distAxY = 0; - } else { - pos.distAxX += Math.abs(pos.distX); - if (pos.dirX !== 0 && pos.dirX !== pos.lastDirX) { - pos.distAxX = 0; - } - pos.distAxY += Math.abs(pos.distY); - if (pos.dirY !== 0 && pos.dirY !== pos.lastDirY) { - pos.distAxY = 0; - } - } - pos.dirAx = newAx; - }, - replaceIndent: function (scope, element, indent, attr) { - attr = attr || 'left'; - angular.element(element.children()[0]).css(attr, scope.$callbacks.calsIndent(indent)); + if (!_$scope.$callbacks.beforeDrag(dragScope, $params.dragInfo)) { + return; } - }; - return _$helper; - }] - ); + $params.firstMoving = true; + _$scope.setDragging($params.dragInfo); -angular.module('ntt.TreeDnD') - .factory('$TreeDnDPlugin', [ - '$injector', - function ($injector) { - var _fnget = function (name) { - if (angular.isDefined($injector) && $injector.has(name)) { - return $injector.get(name); + var eventObj = $TreeDnDHelper.eventObj(e); + $params.pos = $TreeDnDHelper.positionStarted(eventObj, dragScope.$element); + + if (dragScope.isTable) { + $params.dragElm = angular.element($params.$window.document.createElement('table')) + .addClass(_$scope.$class.tree) + .addClass(_$scope.$class.drag) + .addClass(_$scope.$tree_class); + } else { + $params.dragElm = angular.element($params.$window.document.createElement('ul')) + .addClass(_$scope.$class.drag) + .addClass('tree-dnd-nodes') + .addClass(_$scope.$tree_class); } - return null; - }; - return _fnget; - }] - ); -angular.module('ntt.TreeDnD') - .factory('$TreeDnDTemplate', [ - '$templateCache', - function ($templateCache) { - var templatePath = 'template/TreeDnD/TreeDnD.html', - copyPath = 'template/TreeDnD/TreeDnDStatusCopy.html', - movePath = 'template/TreeDnD/TreeDnDStatusMove.html', - scopes = {}, - temp, - _$init = { - setMove: function (path, scope) { - if (!scopes[scope.$id]) { - scopes[scope.$id] = {}; - } - scopes[scope.$id].movePath = path; - }, - setCopy: function (path, scope) { - if (!scopes[scope.$id]) { - scopes[scope.$id] = {}; - } - scopes[scope.$id].copyPath = path; - }, - getPath: function () { - return templatePath; - }, - getCopy: function (scope) { - if (scopes[scope.$id] && scopes[scope.$id].copyPath) { - temp = $templateCache.get(scopes[scope.$id].copyPath); - if (temp) { - return temp; - } - } - return $templateCache.get(copyPath); - }, - getMove: function (scope) { - if (scopes[scope.$id] && scopes[scope.$id].movePath) { - temp = $templateCache.get(scopes[scope.$id].movePath); - if (temp) { - return temp; - } - } - return $templateCache.get(movePath); + $params.dragElm.css( + { + 'width': $TreeDnDHelper.width(dragScope.$element) + 'px', + 'z-index': 9995 } - }; + ); - return _$init; - }] - ); + $params.offsetEdge = 0; + var _width = $TreeDnDHelper.width(dragScope.$element), + _scope = dragScope, + _element = _scope.$element, + _clone, + _needCollapse = !!_$scope.enabledCollapse, + _copied = false, + _tbody, + _frag; -angular.module('ntt.TreeDnD') - .factory('$TreeDnDViewport', fnInitTreeDnDViewport); + if (_scope.isTable) { + $params.offsetEdge = $params.dragInfo.node.__level__ - 1; + _tbody = angular.element(document.createElement('tbody')); + _frag = angular.element(document.createDocumentFragment()); -fnInitTreeDnDViewport.$inject = ['$window', '$document', '$timeout', '$q', '$compile']; + _$scope.for_all_descendants( + $params.dragInfo.node, function (_node, _parent) { + _scope = _$scope.getScope(_node); + _element = _scope && _scope.$element; + if (_scope && _element) { + if (!_copied) { + _clone = _element.clone(); -function fnInitTreeDnDViewport($window, $document, $timeout, $q, $compile) { + $TreeDnDHelper.replaceIndent( + _$scope, + _clone, + _node.__level__ - $params.offsetEdge, + 'padding-left' + ); - var viewport = null, - isUpdating = false, - isRender = false, - updateAgain = false, - viewportRect, - items = [], - nodeTemplate, - updateTimeout, - renderTime, - $initViewport = { - setViewport: setViewport, - getViewport: getViewport, - add: add, - setTemplate: setTemplate, - getItems: getItems, - updateDelayed: updateDelayed - }, - eWindow = angular.element($window); + _frag.append(_clone); - eWindow.on('load resize scroll', updateDelayed); + // skip all, just clone parent + if (_needCollapse) { + _copied = true; + } - return $initViewport; + // hide if have status Move; + if (_$scope.enabledMove && _$scope.$class.hidden && + (!_parent || _node.__visible__ || _parent.__visible__ && _parent.__expanded__)) { + _element.addClass(_$scope.$class.hidden); + } + } + } + // skip children of node not expand. + return _copied || _node.__visible__ === false || _node.__expanded__ === false; - function update() { + }, null, !_needCollapse + ); + _tbody.append(_frag); + $params.dragElm.append(_tbody); + } else { - viewportRect = { - width: eWindow.prop('offsetWidth') || document.documentElement.clientWidth, - height: eWindow.prop('offsetHeight') || document.documentElement.clientHeight, - top: $document[0].body.scrollTop || $document[0].documentElement.scrollTop, - left: $document[0].body.scrollLeft || $document[0].documentElement.scrollLeft - }; + _clone = _element.clone(); + if (_needCollapse) { + _clone[0].querySelector('[tree-dnd-nodes]').remove(); + } - if (isUpdating || isRender) { - updateAgain = true; - return; - } - isUpdating = true; + // hide if have status Move; + $params.dragElm.append(_clone); + if (_$scope.enabledMove && _$scope.$class.hidden) { + _element.addClass(_$scope.$class.hidden); + } + } - recursivePromise(); - } + $params.dragElm.css( + { + 'left': eventObj.pageX - $params.pos.offsetX + _$scope.$callbacks.calsIndent( + $params.offsetEdge + 1, + true, + true + ) + 'px', + 'top': eventObj.pageY - $params.pos.offsetY + 'px' + } + ); + // moving item with descendant + $params.$document.find('body').append($params.dragElm); + if (_$scope.$callbacks.droppable()) { + $params.placeElm = _$scope.initPlace(dragScope.$element, $params.dragElm); - function recursivePromise() { - if (isRender) { - return; - } + if (dragScope.isTable) { + $TreeDnDHelper.replaceIndent(_$scope, $params.placeElm, $params.dragInfo.node.__level__); + } - var number = number > 0 ? number : items.length, item; + $params.placeElm.css('width', _width); + } - if (number > 0) { - item = items[0]; + _$scope.showPlace(); + _$scope.targeting = true; - isRender = true; - renderTime = $timeout(function () { - //item.element.html(nodeTemplate); - //$compile(item.element.contents())(item.scope); + if (_$scope.enabledStatus) { + _$scope.refreshStatus(); + _$scope.setPositionStatus(e); + } - items.splice(0, 1); - isRender = false; - number--; - $timeout.cancel(renderTime); - recursivePromise(); - }, 0); + angular.element($params.$document).bind('touchend', $params.dragEndEvent); + angular.element($params.$document).bind('touchcancel', $params.dragEndEvent); + angular.element($params.$document).bind('touchmove', $params.dragMoveEvent); + angular.element($params.$document).bind('mouseup', $params.dragEndEvent); + angular.element($params.$document).bind('mousemove', $params.dragMoveEvent); + angular.element($params.$document).bind('mouseleave', $params.dragCancelEvent); - } else { - isUpdating = false; - if (updateAgain) { - updateAgain = false; - update(); - } - } + $params.document_height = Math.max( + $params.body.scrollHeight, + $params.body.offsetHeight, + $params.html.clientHeight, + $params.html.scrollHeight, + $params.html.offsetHeight + ); - } + $params.document_width = Math.max( + $params.body.scrollWidth, + $params.body.offsetWidth, + $params.html.clientWidth, + $params.html.scrollWidth, + $params.html.offsetWidth + ); + } - /** - * Check if a point is inside specified bounds - * @param x - * @param y - * @param bounds - * @returns {boolean} - */ - function pointIsInsideBounds(x, y, bounds) { - return x >= bounds.left && - y >= bounds.top && - x <= bounds.left + bounds.width && - y <= bounds.top + bounds.height; - } + function _fnDragMove(e, $params) { + var _$scope = $params.$scope; + if (!$params.dragStarted) { + if (!$params.dragDelaying) { + $params.dragStarted = true; + _$scope.$safeApply( + function () { + _$scope.$callbacks.dragStart($params.dragInfo); + } + ); + } + return; + } - /** - * @name setViewport - * @desciption Set the viewport element - * @param element - */ - function setViewport(element) { - viewport = element; - } + if ($params.dragElm) { + e.preventDefault(); + if ($params.$window.getSelection) { + $params.$window.getSelection().removeAllRanges(); + } else if ($params.$window.document.selection) { + $params.$window.document.selection.empty(); + } - /** - * Return the current viewport - * @returns {*} - */ - function getViewport() { - return viewport; - } + var eventObj = $TreeDnDHelper.eventObj(e), + leftElmPos = eventObj.pageX - $params.pos.offsetX, + topElmPos = eventObj.pageY - $params.pos.offsetY; - /** - * trigger an update - */ - function updateDelayed() { - $timeout.cancel(updateTimeout); - updateTimeout = $timeout(function () { - update(); - }, 0); - } + //dragElm can't leave the screen on the left + if (leftElmPos < 0) { + leftElmPos = 0; + } - /** - * Add listener for event - * @param element - * @param callback - */ - function add(scope, element) { - updateDelayed(); - items.push({ - element: element, - scope: scope - }); - } + //dragElm can't leave the screen on the top + if (topElmPos < 0) { + topElmPos = 0; + } - function setTemplate(scope, template) { - nodeTemplate = template; - } + //dragElm can't leave the screen on the bottom + if (topElmPos + 10 > $params.document_height) { + topElmPos = $params.document_height - 10; + } - /** - * Get list of items - * @returns {Array} - */ - function getItems() { - return items; - } -} + //dragElm can't leave the screen on the right + if (leftElmPos + 10 > $params.document_width) { + leftElmPos = $params.document_width - 10; + } -angular.module('ntt.TreeDnD') - .factory('$TreeDnDFilter', [ - '$filter', function ($filter) { - return fnInitFilter; + $params.dragElm.css( + { + 'left': leftElmPos + _$scope.$callbacks.calsIndent( + $params.offsetEdge + 1, + true, + true + ) + 'px', + 'top': topElmPos + 'px' + } + ); - function for_all_descendants(options, node, fieldChild, fnBefore, fnAfter, parentPassed) { - if (!angular.isFunction(fnBefore)) { - return null; - } + if (_$scope.enabledStatus) { + _$scope.setPositionStatus(e); + } - var _i, _len, _nodes, - _nodePassed = fnBefore(options, node), - _childPassed = false, - _filter_index = options.filter_index; + var top_scroll = window.pageYOffset || $params.$window.document.documentElement.scrollTop, + bottom_scroll = top_scroll + (window.innerHeight || $params.$window.document.clientHeight || $params.$window.document.clientHeight); + // to scroll down if cursor y-position is greater than the bottom position the vertical scroll + if (bottom_scroll < eventObj.pageY && bottom_scroll <= $params.document_height) { + window.scrollBy(0, 10); + } + // to scroll top if cursor y-position is less than the top position the vertical scroll + if (top_scroll > eventObj.pageY) { + window.scrollBy(0, -10); + } - if (angular.isDefined(node[fieldChild])) { - _nodes = node[fieldChild]; - _len = _nodes.length; + $TreeDnDHelper.positionMoved(e, $params.pos, $params.firstMoving); - options.filter_index = 0; - for (_i = 0; _i < _len; _i++) { - _childPassed = for_all_descendants( - options, - _nodes[_i], - fieldChild, - fnBefore, - fnAfter, - _nodePassed || parentPassed - ) || _childPassed; + if ($params.firstMoving) { + $params.firstMoving = false; + return; } + // check if add it as a child node first - // restore filter_index of node - options.filter_index = _filter_index; - } + var targetX = eventObj.pageX - $params.$window.document.body.scrollLeft, + targetY = eventObj.pageY - (window.pageYOffset || $params.$window.document.documentElement.scrollTop), - if (angular.isFunction(fnAfter)) { - fnAfter(options, node, _nodePassed === true, _childPassed === true, parentPassed === true); - } + targetElm, + targetScope, + targetBefore, + targetOffset, + isChanged = true, + isVeritcal = true, + isEmpty, + isSwapped, + _scope, + _target, + _parent, + _info = $params.dragInfo, + _move = _info.move, + _drag = _info.node, + _drop = _info.drop, + treeScope = _info.target, + fnSwapTree, + isHolder = _fnPlaceHolder(e, $params); - return _nodePassed || _childPassed; - } + if (!isHolder) { + /* when using elementFromPoint() inside an iframe, you have to call + elementFromPoint() twice to make sure IE8 returns the correct value + $params.$window.document.elementFromPoint(targetX, targetY);*/ - /** - * Check data with callback - * @param {string|object|function|regex} callback - * @param {*} data - * @returns {null|boolean} - * @private - */ - function _fnCheck(callback, data) { - if (angular.isUndefinedOrNull(data) || angular.isArray(data)) { - return null; - } + targetElm = angular.element( + $params.$window.document.elementFromPoint( + targetX, + targetY + ) + ); - if (angular.isFunction(callback)) { - return callback(data, $filter); - } else { - if (typeof callback === 'boolean') { - data = !!data; - return data === callback; - } else if (angular.isDefined(callback)) { - try { - var _regex = new RegExp(callback); - return _regex.test(data); - } - catch (err) { - if (typeof data === 'string') { - return data.indexOf(callback) > -1; - } else { - return null; - } + targetScope = targetElm.scope(); + if (!targetScope || !targetScope.$callbacks || !targetScope.$callbacks.droppable()) { + // Not allowed Drop Item + return; } - } else { - return null; - } - } - } - /** - * `fnProcess` to call `_fnCheck`. If `condition` is `array` then call `for_each_filter` - * else will call `_fnCheck`. Specical `condition.field` is `_$` then apply `condition.callback` for all field, if have `field` invaild then `return true`. - * - * @param node - * @param condition - * @param isAnd - * @returns {null|boolean} - * @private - */ - function _fnProccess(node, condition, isAnd) { - if (angular.isArray(condition)) { - return for_each_filter(node, condition, isAnd); - } else { - var _key = condition.field, - _callback = condition.callback, - _iO, _keysO, _lenO; + fnSwapTree = function () { + treeScope = targetScope.getScopeTree(); + _target = _info.target; - if (_key === '_$') { - _keysO = Object.keys(node); - _lenO = _keysO.length; - for (_iO = 0; _iO < _lenO; _iO++) { - if (_fnCheck(_callback, node[_keysO[_iO]])) { - return true; - } - } - } else if (angular.isDefined(node[_key])) { - return _fnCheck(_callback, node[_key]); - } - } - return null; - } + if (_info.target !== treeScope) { + // Replace by place-holder new + _target.hidePlace(); + _target.targeting = false; + treeScope.targeting = true; - /** - * - * @param {object} node - * @param {array} conditions Array `conditions` - * @param {boolean} isAnd check with condition `And`, if `And` then `return false` when all `false` - * @returns {null|boolean} - */ - function for_each_filter(node, conditions, isAnd) { - var i, len = conditions.length || 0, passed = false; - if (len === 0) { - return null; - } + _info.target = treeScope; + $params.placeElm = treeScope.initPlace(targetScope.$element, $params.dragElm); - for (i = 0; i < len; i++) { - if (_fnProccess(node, conditions[i], !isAnd)) { - passed = true; - // if condition `or` then return; - if (!isAnd) { + _target = null; + isSwapped = true; + } return true; - } - } else { + }; - // if condition `and` and result in fnProccess = false then return; - if (isAnd) { - return false; + if (angular.isFunction(targetScope.getScopeNode)) { + targetScope = targetScope.getScopeNode(); + if (!fnSwapTree()) { + return; + } + } else { + if (targetScope.$type === 'TreeDnDNodes' || targetScope.$type === 'TreeDnD') { + if (targetScope.tree_nodes) { + if (targetScope.tree_nodes.length === 0) { + if (!fnSwapTree()) { + return; + } + // Empty + isEmpty = true; + } + } else { + return; + } + } else { + return; + } } } - } - return passed; - } + if ($params.pos.dirAx && !isSwapped || isHolder) { + isVeritcal = false; + targetScope = _info.scope; + } - /** - * Will call _fnAfter to clear data no need - * @param {object} options - * @param {object} node - * @param {boolean} isNodePassed - * @param {boolean} isChildPassed - * @param {boolean} isParentPassed - * @private - */ - function _fnAfter(options, node, isNodePassed, isChildPassed, isParentPassed) { - if (isNodePassed === true) { - node.__filtered__ = true; - node.__filtered_visible__ = true; - node.__filtered_index__ = options.filter_index++; - return; //jmp - } else if (isChildPassed === true && options.showParent === true - || isParentPassed === true && options.showChild === true) { - node.__filtered__ = false; - node.__filtered_visible__ = true; - node.__filtered_index__ = options.filter_index++; - return; //jmp - } + if (!targetScope.$element && !targetScope) { + return; + } - // remove attr __filtered__ - delete node.__filtered__; - delete node.__filtered_visible__; - delete node.__filtered_index__; - } + if (isEmpty) { + _move.parent = null; + _move.pos = 0; - /** - * `fnBefore` will called when `for_all_descendants` of `node` checking. - * If `filter` empty then return `true` else result of function `_fnProccess` {@see _fnProccess} - * - * @param {object} options - * @param {object} node - * @returns {null|boolean} - * @private - */ - function _fnBefore(options, node) { - if (options.filter.length === 0) { - return true; - } else { - return _fnProccess(node, options.filter, options.beginAnd || false); - } - } + _drop = null; + } else { + // move vertical + if (isVeritcal) { + targetElm = targetScope.$element; // Get the element of tree-dnd-node + if (angular.isUndefinedOrNull(targetElm)) { + return; + } + targetOffset = $TreeDnDHelper.offset(targetElm); - /** - * `fnBeforeClear` will called when `for_all_descendants` of `node` checking. - * Alway false to Clear Filter empty - * - * @param {object} options - * @param {object} node - * @returns {null|boolean} - * @private - */ - function _fnBeforeClear(options, node) { - return false; - } + if (targetScope.horizontal && !targetScope.isTable) { + targetBefore = eventObj.pageX < targetOffset.left + $TreeDnDHelper.width(targetElm) / 2; + } else { + if (targetScope.isTable) { + targetBefore = eventObj.pageY < targetOffset.top + $TreeDnDHelper.height(targetElm) / 2; + } else { + var _height = $TreeDnDHelper.height(targetElm); - /** - * `_fnConvert` to convert `filter` `object` to `array` invaild. - * - * @param {object|array} filters - * @returns {array} Instead of `filter` or new array invaild *(converted from filter)* - * @private - */ - function _fnConvert(filters) { - var _iF, _lenF, _keysF, - _filter, - _state; - // convert filter object to array filter - if (angular.isObject(filters) && !angular.isArray(filters)) { - _keysF = Object.keys(filters); - _lenF = _keysF.length; - _filter = []; + if (targetScope.getElementChilds()) { + _height -= -$TreeDnDHelper.height(targetScope.getElementChilds()); + } - if (_lenF > 0) { - for (_iF = 0; _iF < _lenF; _iF++) { + if (eventObj.pageY > targetOffset.top + _height) { + return; + } - if (typeof filters[_keysF[_iF]] === 'string' && filters[_keysF[_iF]].length === 0) { - continue; - } else if (angular.isArray(filters[_keysF[_iF]])) { - _state = filters[_keysF[_iF]]; - } else if (angular.isObject(filters[_keysF[_iF]])) { - _state = _fnConvert(filters[_keysF[_iF]]); - } else { - _state = { - field: _keysF[_iF], - callback: filters[_keysF[_iF]] - }; + targetBefore = eventObj.pageY < targetOffset.top + _height / 2; + } } - _filter.push(_state); - } - } - _state = null; - return _filter; - } - else { - return filters; - } - } - /** - * `fnInitFilter` function is constructor of service `$TreeDnDFilter`. - * @constructor - * @param {object|array} treeData - * @param {object|array} filters - * @param {object} options - * @param {string} keyChild - * @returns {array} Return `treeData` or `treeData` with `filter` - * @private - */ - function fnInitFilter(treeData, filters, options, keyChild) { - if (!angular.isArray(treeData) - || treeData.length === 0) { - return treeData; - } + if (!angular.isFunction(targetScope.getData)) { + return; + } - var _i, _len, - _filter; + _target = targetScope.getData(); + _parent = targetScope.getNode(_target.__parent_real__); - _filter = _fnConvert(filters); - if (!(angular.isArray(_filter) || angular.isObject(_filter)) - || _filter.length === 0) { - for (_i = 0, _len = treeData.length; _i < _len; _i++) { - for_all_descendants( - options, - treeData[_i], - keyChild || '__children__', - _fnBeforeClear, _fnAfter - ); - } - return treeData; - } + if (targetBefore) { + var _prev = targetScope.getPrevSibling(_target); - options.filter = _filter; - options.filter_index = 0; - for (_i = 0, _len = treeData.length; _i < _len; _i++) { - for_all_descendants( - options, - treeData[_i], - keyChild || '__children__', - _fnBefore, _fnAfter - ); - } + _move.parent = _parent; + _move.pos = angular.isDefined(_prev) ? _prev.__index__ + 1 : 0; - return treeData; - } + _drop = _prev; + } else { + if (_target.__expanded__ && !(_target.__children__.length === 1 && _target.__index_real__ === _drag.__parent_real__)) { + _move.parent = _target; + _move.pos = 0; - }] - ); + _drop = null; + } else { + _move.parent = _parent; + _move.pos = _target.__index__ + 1; -angular.module('ntt.TreeDnD') - .factory('$TreeDnDOrderBy', [ - '$filter', - function ($filter) { - var _fnOrderBy = $filter('orderBy'), - for_all_descendants = function for_all_descendants(options, node, name, fnOrderBy) { - var _i, _len, _nodes; + _drop = _target; + } + } + } else { + // move horizontal + if ($params.pos.dirAx && $params.pos.distAxX >= treeScope.dragBorder) { + $params.pos.distAxX = 0; + // increase horizontal level if previous sibling exists and is not collapsed + if ($params.pos.distX > 0) { + _parent = _drop; + if (!_parent) { + if (_move.pos - 1 >= 0) { + _parent = _move.parent.__children__[_move.pos - 1]; + } else { + return; + } + } - if (angular.isDefined(node[name])) { - _nodes = node[name]; - _len = _nodes.length; - // OrderBy children - for (_i = 0; _i < _len; _i++) { - _nodes[_i] = for_all_descendants(options, _nodes[_i], name, fnOrderBy); - } + if (_info.drag === _info.target && _parent === _drag && _$scope.enabledMove) { + _parent = treeScope.getPrevSibling(_parent); + } - node[name] = fnOrderBy(node[name], options); - } - return node; - }, - _fnOrder = function _fnOrder(list, orderBy) { - return _fnOrderBy(list, orderBy); - }, - _fnMain = function _fnMain(treeData, orderBy) { - if (!angular.isArray(treeData) - || treeData.length === 0 - || !(angular.isArray(orderBy) || angular.isObject(orderBy) || angular.isString(orderBy) || angular.isFunction(orderBy)) - || orderBy.length === 0 && !angular.isFunction(orderBy)) { - return treeData; - } + if (_parent && _parent.__visible__) { + var _len = _parent.__children__.length; - var _i, _len; + _move.parent = _parent; + _move.pos = _len; - for (_i = 0, _len = treeData.length; _i < _len; _i++) { - treeData[_i] = for_all_descendants( - orderBy, - treeData[_i], - '__children__', - _fnOrder - ); - } + if (_len > 0) { + _drop = _parent.__children__[_len - 1]; + } else { + _drop = null; + } + } else { + // Not changed + return; + } + } else if ($params.pos.distX < 0) { + _target = _move.parent; + if (_target && + (_target.__children__.length === 0 || + _target.__children__.length - 1 < _move.pos || + _info.drag === _info.target && + _target.__index_real__ === _drag.__parent_real__ && + _target.__children__.length - 1 === _drag.__index__ && _$scope.enabledMove) + ) { + _parent = treeScope.getNode(_target.__parent_real__); - return _fnOrder(treeData, orderBy); - }; + _move.parent = _parent; + _move.pos = _target.__index__ + 1; - return _fnMain; - }] - ); + _drop = _target; + } else { + // Not changed + return; + } + } else { + return; + } + } else { + // limited + return; + } + } + } -angular.module('ntt.TreeDnD') - .factory('$TreeDnDDrag', [ - '$timeout', '$TreeDnDHelper', - function ($timeout, $TreeDnDHelper) { - function _fnPlaceHolder(e, $params) { - if ($params.placeElm) { - var _offset = $TreeDnDHelper.offset($params.placeElm); - if (_offset.top <= e.pageY && e.pageY <= _offset.top + _offset.height && - _offset.left <= e.pageX && e.pageX <= _offset.left + _offset.width + if (_info.drag === _info.target && + _move.parent && + _drag.__parent_real__ === _move.parent.__index_real__ && + _drag.__index__ === _move.pos ) { - return true; + isChanged = false; } - } - return false; - } - - function _fnDragStart(e, $params) { - if (!$params.hasTouch && (e.button === 2 || e.which === 3)) { - // disable right click - return; - } - if (e.uiTreeDragging || e.originalEvent && e.originalEvent.uiTreeDragging) { // event has already fired in other scope. - return; - } + if (treeScope.$callbacks.accept(_info, _move, isChanged)) { + _info.move = _move; + _info.drop = _drop; + _info.changed = isChanged; + _info.scope = targetScope; - // the element which is clicked. - var eventElm = angular.element(e.target), - eventScope = eventElm.scope(); - if (!eventScope || !eventScope.$type) { - return; - } - // if (eventScope.$type !== 'TreeDnDNode') { // Check if it is a node or a handle - // return; - // } + if (targetScope.isTable) { + $TreeDnDHelper.replaceIndent( + treeScope, + $params.placeElm, + angular.isUndefinedOrNull(_move.parent) ? 1 : _move.parent.__level__ + 1 + ); - if (eventScope.$type !== 'TreeDnDNodeHandle') { // If the node has a handle, then it should be clicked by the handle - return; - } + if (_drop) { + _parent = (_move.parent ? _move.parent.__children__ : null) || _info.target.treeData; - var eventElmTagName = eventElm.prop('tagName').toLowerCase(), - dragScope, - _$scope = $params.$scope; - if (eventElmTagName === 'input' - || eventElmTagName === 'textarea' - || eventElmTagName === 'button' - || eventElmTagName === 'select') { // if it's a input or button, ignore it - return; - } - // check if it or it's parents has a 'data-nodrag' attribute - while (eventElm && eventElm[0] && eventElm[0] !== $params.element) { - if ($TreeDnDHelper.nodrag(eventElm)) { // if the node mark as `nodrag`, DONOT drag it. - return; - } - eventElm = eventElm.parent(); - } + if (_drop.__index__ < _parent.length - 1) { + // Find fast + _drop = _parent[_drop.__index__ + 1]; + _scope = _info.target.getScope(_drop); + _scope.$element[0].parentNode.insertBefore( + $params.placeElm[0], + _scope.$element[0] + ); + } else { + _target = _info.target.getLastDescendant(_drop); + _scope = _info.target.getScope(_target); + _scope.$element.after($params.placeElm); + } + } else { + _scope = _info.target.getScope(_move.parent); + if (_scope) { + if (_move.parent) { + _scope.$element.after($params.placeElm); - e.uiTreeDragging = true; // stop event bubbling - if (e.originalEvent) { - e.originalEvent.uiTreeDragging = true; - } - e.preventDefault(); + } else { + _scope.getElementChilds().prepend($params.placeElm); + } + } + } + } else { + _scope = _info.target.getScope(_drop || _move.parent); + if (_drop) { + _scope.$element.after($params.placeElm); + } else { + _scope.getElementChilds().prepend($params.placeElm); + } + } - dragScope = eventScope.getScopeNode(); + treeScope.showPlace(); - $params.dragInfo = $TreeDnDHelper.dragInfo(dragScope); + _$scope.$safeApply( + function () { + _$scope.$callbacks.dragMove(_info); + } + ); + } - if (!_$scope.$callbacks.beforeDrag(dragScope, $params.dragInfo)) { - return; } + } - $params.firstMoving = true; - _$scope.setDragging($params.dragInfo); + function _fnDragEnd(e, $params) { + e.preventDefault(); - var eventObj = $TreeDnDHelper.eventObj(e); - $params.pos = $TreeDnDHelper.positionStarted(eventObj, dragScope.$element); + // Always unbind document listeners first to prevent leaks + // even if dragElm is null (edge case handling) + _fnUnbindDocumentListeners($params); - if (dragScope.isTable) { - $params.dragElm = angular.element($params.$window.document.createElement('table')) - .addClass(_$scope.$class.tree) - .addClass(_$scope.$class.drag) - .addClass(_$scope.$tree_class); - } else { - $params.dragElm = angular.element($params.$window.document.createElement('ul')) - .addClass(_$scope.$class.drag) - .addClass('tree-dnd-nodes') - .addClass(_$scope.$tree_class); - } - - $params.dragElm.css( - { - 'width': $TreeDnDHelper.width(dragScope.$element) + 'px', - 'z-index': 9995 - } - ); - - $params.offsetEdge = 0; - var _width = $TreeDnDHelper.width(dragScope.$element), - _scope = dragScope, - _element = _scope.$element, - _clone, - _needCollapse = !!_$scope.enabledCollapse, - _copied = false, - _tbody, - _frag; - - if (_scope.isTable) { - $params.offsetEdge = $params.dragInfo.node.__level__ - 1; - _tbody = angular.element(document.createElement('tbody')); - _frag = angular.element(document.createDocumentFragment()); - - _$scope.for_all_descendants( - $params.dragInfo.node, function (_node, _parent) { - _scope = _$scope.getScope(_node); - _element = _scope && _scope.$element; - if (_scope && _element) { - if (!_copied) { - _clone = _element.clone(); - - $TreeDnDHelper.replaceIndent( - _$scope, - _clone, - _node.__level__ - $params.offsetEdge, - 'padding-left' - ); - - _frag.append(_clone); + if ($params.dragElm) { + var _passed = false, + _$scope = $params.$scope, + _scope = _$scope.getScope($params.dragInfo.node), + _element = _scope.$element; - // skip all, just clone parent - if (_needCollapse) { - _copied = true; - } + _$scope.$safeApply( + function () { + _passed = _$scope.$callbacks.beforeDrop($params.dragInfo); + } + ); - // hide if have status Move; - if (_$scope.enabledMove && _$scope.$class.hidden && - (!_parent || _node.__visible__ || _parent.__visible__ && _parent.__expanded__)) { - _element.addClass(_$scope.$class.hidden); + // rollback all + if (_scope.isTable) { + _$scope.for_all_descendants( + $params.dragInfo.node, function (_node, _parent) { + _scope = _$scope.getScope(_node); + _element = _scope && _scope.$element; + if (_scope && _element && (!_parent && _node.__visible__ || _parent.__expanded__)) { + if (_$scope.$class.hidden) { + _element.removeClass(_$scope.$class.hidden); } } - } - // skip children of node not expand. - return _copied || _node.__visible__ === false || _node.__expanded__ === false; + return _node.__visible__ === false || _node.__expanded__ === false + }, null, true + ); + } else { + if (_$scope.$class.hidden) { + _element.removeClass(_$scope.$class.hidden); + } + } - }, null, !_needCollapse - ); - _tbody.append(_frag); - $params.dragElm.append(_tbody); - } else { + $params.dragElm.remove(); + $params.dragElm = null; - _clone = _element.clone(); - if (_needCollapse) { - _clone[0].querySelector('[tree-dnd-nodes]').remove(); + if (_$scope.enabledStatus) { + _$scope.hideStatus(); } - // hide if have status Move; - $params.dragElm.append(_clone); - if (_$scope.enabledMove && _$scope.$class.hidden) { - _element.addClass(_$scope.$class.hidden); + if (_$scope.$$apply) { + _$scope.$safeApply( + function () { + var _status = _$scope.$callbacks.dropped( + $params.dragInfo, + _passed + ); + + _$scope.$callbacks.dragStop($params.dragInfo, _status); + clearData(); + } + ); + } else { + _fnBindDrag($params); + _$scope.$safeApply( + function () { + _$scope.$callbacks.dragStop($params.dragInfo, false); + clearData(); + } + ); } + } - $params.dragElm.css( - { - 'left': eventObj.pageX - $params.pos.offsetX + _$scope.$callbacks.calsIndent( - $params.offsetEdge + 1, - true, - true - ) + 'px', - 'top': eventObj.pageY - $params.pos.offsetY + 'px' + function clearData() { + if ($params.dragInfo && $params.dragInfo.target) { + $params.dragInfo.target.hidePlace(); + $params.dragInfo.target.targeting = false; } - ); - // moving item with descendant - $params.$document.find('body').append($params.dragElm); - if (_$scope.$callbacks.droppable()) { - $params.placeElm = _$scope.initPlace(dragScope.$element, $params.dragElm); - if (dragScope.isTable) { - $TreeDnDHelper.replaceIndent(_$scope, $params.placeElm, $params.dragInfo.node.__level__); + $params.dragInfo = null; + if ($params.$scope) { + $params.$scope.$$apply = false; + $params.$scope.setDragging(null); } - - $params.placeElm.css('width', _width); } + } - _$scope.showPlace(); - _$scope.targeting = true; + /** + * Unbind all document-level event listeners + * Separated into its own function to ensure proper cleanup + */ + function _fnUnbindDocumentListeners($params) { + angular.element($params.$document).unbind('touchend', $params.dragEndEvent); + angular.element($params.$document).unbind('touchcancel', $params.dragEndEvent); + angular.element($params.$document).unbind('touchmove', $params.dragMoveEvent); + angular.element($params.$document).unbind('mouseup', $params.dragEndEvent); + angular.element($params.$document).unbind('mousemove', $params.dragMoveEvent); + angular.element($params.$window.document.body).unbind('mouseleave', $params.dragCancelEvent); + } - if (_$scope.enabledStatus) { - _$scope.refreshStatus(); - _$scope.setPositionStatus(e); + function _fnDragStartEvent(e, $params) { + if ($params.$scope.$callbacks.draggable()) { + _fnDragStart(e, $params); } - - angular.element($params.$document).bind('touchend', $params.dragEndEvent); - angular.element($params.$document).bind('touchcancel', $params.dragEndEvent); - angular.element($params.$document).bind('touchmove', $params.dragMoveEvent); - angular.element($params.$document).bind('mouseup', $params.dragEndEvent); - angular.element($params.$document).bind('mousemove', $params.dragMoveEvent); - angular.element($params.$document).bind('mouseleave', $params.dragCancelEvent); - - $params.document_height = Math.max( - $params.body.scrollHeight, - $params.body.offsetHeight, - $params.html.clientHeight, - $params.html.scrollHeight, - $params.html.offsetHeight - ); - - $params.document_width = Math.max( - $params.body.scrollWidth, - $params.body.offsetWidth, - $params.html.clientWidth, - $params.html.scrollWidth, - $params.html.offsetWidth - ); } - function _fnDragMove(e, $params) { - var _$scope = $params.$scope; - if (!$params.dragStarted) { - if (!$params.dragDelaying) { - $params.dragStarted = true; - _$scope.$safeApply( + function _fnBindDrag($params) { + $params.element.bind( + 'touchstart mousedown', function (e) { + $params.dragDelaying = true; + $params.dragStarted = false; + _fnDragStartEvent(e, $params); + $params.dragTimer = $timeout( function () { - _$scope.$callbacks.dragStart($params.dragInfo); - } + $params.dragDelaying = false; + }, $params.$scope.dragDelay ); } - return; - } - - if ($params.dragElm) { - e.preventDefault(); - if ($params.$window.getSelection) { - $params.$window.getSelection().removeAllRanges(); - } else if ($params.$window.document.selection) { - $params.$window.document.selection.empty(); - } - - var eventObj = $TreeDnDHelper.eventObj(e), - leftElmPos = eventObj.pageX - $params.pos.offsetX, - topElmPos = eventObj.pageY - $params.pos.offsetY; - - //dragElm can't leave the screen on the left - if (leftElmPos < 0) { - leftElmPos = 0; - } + ); - //dragElm can't leave the screen on the top - if (topElmPos < 0) { - topElmPos = 0; + $params.element.bind( + 'touchend touchcancel mouseup', function () { + $timeout.cancel($params.dragTimer); } + ); + } - //dragElm can't leave the screen on the bottom - if (topElmPos + 10 > $params.document_height) { - topElmPos = $params.document_height - 10; + function _fnKeydownHandler(e, $params) { + var _$scope = $params.$scope; + if (e.keyCode === 27) { + if (_$scope.enabledStatus) { + _$scope.hideStatus(); } - //dragElm can't leave the screen on the right - if (leftElmPos + 10 > $params.document_width) { - leftElmPos = $params.document_width - 10; - } + _$scope.$$apply = false; + _fnDragEnd(e, $params); + } else { + if (_$scope.enabledHotkey && e.shiftKey) { + _$scope.enableMove(true); + if (_$scope.enabledStatus) { + _$scope.refreshStatus(); + } - $params.dragElm.css( - { - 'left': leftElmPos + _$scope.$callbacks.calsIndent( - $params.offsetEdge + 1, - true, - true - ) + 'px', - 'top': topElmPos + 'px' + if (!$params.dragInfo) { + return; } - ); - if (_$scope.enabledStatus) { - _$scope.setPositionStatus(e); - } + var _scope = _$scope.getScope($params.dragInfo.node), + _element = _scope.$element; - var top_scroll = window.pageYOffset || $params.$window.document.documentElement.scrollTop, - bottom_scroll = top_scroll + (window.innerHeight || $params.$window.document.clientHeight || $params.$window.document.clientHeight); - // to scroll down if cursor y-position is greater than the bottom position the vertical scroll - if (bottom_scroll < eventObj.pageY && bottom_scroll <= $params.document_height) { - window.scrollBy(0, 10); - } - // to scroll top if cursor y-position is less than the top position the vertical scroll - if (top_scroll > eventObj.pageY) { - window.scrollBy(0, -10); + if (_scope.isTable) { + _$scope.for_all_descendants( + $params.dragInfo.node, function (_node, _parent) { + _scope = _$scope.getScope(_node); + _element = _scope && _scope.$element; + if (_scope && _element && (!_parent && _node.__visible__ || _parent.__expanded__)) { + if (_$scope.$class.hidden) { + _element.addClass(_$scope.$class.hidden); + } + } + return _node.__visible__ === false || _node.__expanded__ === false + + }, null, true + ); + } else { + if (_$scope.$class.hidden) { + _element.addClass(_$scope.$class.hidden); + } + } } + } + } - $TreeDnDHelper.positionMoved(e, $params.pos, $params.firstMoving); + function _fnKeyupHandler(e, $params) { + var _$scope = $params.$scope; + if (_$scope.enabledHotkey && !e.shiftKey) { + _$scope.enableMove(false); - if ($params.firstMoving) { - $params.firstMoving = false; - return; + if (_$scope.enabledStatus) { + _$scope.refreshStatus(); } - // check if add it as a child node first - - var targetX = eventObj.pageX - $params.$window.document.body.scrollLeft, - targetY = eventObj.pageY - (window.pageYOffset || $params.$window.document.documentElement.scrollTop), - targetElm, - targetScope, - targetBefore, - targetOffset, - isChanged = true, - isVeritcal = true, - isEmpty, - isSwapped, - _scope, - _target, - _parent, - _info = $params.dragInfo, - _move = _info.move, - _drag = _info.node, - _drop = _info.drop, - treeScope = _info.target, - fnSwapTree, - isHolder = _fnPlaceHolder(e, $params); + if (!$params.dragInfo) { + return; + } - if (!isHolder) { - /* when using elementFromPoint() inside an iframe, you have to call - elementFromPoint() twice to make sure IE8 returns the correct value - $params.$window.document.elementFromPoint(targetX, targetY);*/ + var _scope = _$scope.getScope($params.dragInfo.node), + _element = _scope.$element; - targetElm = angular.element( - $params.$window.document.elementFromPoint( - targetX, - targetY - ) + if (_scope.isTable) { + _$scope.for_all_descendants( + $params.dragInfo.node, function (_node, _parent) { + _scope = _$scope.getScope(_node); + _element = _scope && _scope.$element; + if (_scope && _element && (!_parent && _node.__visible__ || _parent.__expanded__)) { + if (_$scope.$class.hidden) { + _element.removeClass(_$scope.$class.hidden); + } + } + return _node.__visible__ === false || _node.__expanded__ === false + }, null, true ); + } else { + if (_$scope.$class.hidden) { + _element.removeClass(_$scope.$class.hidden); + } + } + } + } - targetScope = targetElm.scope(); - if (!targetScope || !targetScope.$callbacks || !targetScope.$callbacks.droppable()) { - // Not allowed Drop Item - return; + function _$init(scope, element, $window, $document) { + var $params = { + hasTouch: 'ontouchstart' in window, + firstMoving: null, + dragInfo: null, + pos: null, + placeElm: null, + dragElm: null, + dragDelaying: true, + dragStarted: false, + dragTimer: null, + body: document.body, + html: document.documentElement, + document_height: null, + document_width: null, + offsetEdge: null, + $scope: scope, + $window: $window, + $document: $document, + element: element, + bindDrag: function () { + _fnBindDrag($params); + }, + dragEnd: function (e) { + _fnDragEnd(e, $params); + }, + dragMoveEvent: function (e) { + _fnDragMove(e, $params); + }, + dragEndEvent: function (e) { + scope.$$apply = true; + _fnDragEnd(e, $params); + }, + dragCancelEvent: function (e) { + _fnDragEnd(e, $params); } + }, + keydownHandler = function (e) { + return _fnKeydownHandler(e, $params); + }, + keyupHandler = function (e) { + return _fnKeyupHandler(e, $params); + }; - fnSwapTree = function () { - treeScope = targetScope.getScopeTree(); - _target = _info.target; + scope.dragEnd = function (e) { + $params.dragEnd(e); + }; - if (_info.target !== treeScope) { - // Replace by place-holder new - _target.hidePlace(); - _target.targeting = false; - treeScope.targeting = true; + $params.bindDrag(); - _info.target = treeScope; - $params.placeElm = treeScope.initPlace(targetScope.$element, $params.dragElm); + angular.element($window.document.body).bind('keydown', keydownHandler); + angular.element($window.document.body).bind('keyup', keyupHandler); + //unbind handler that retains scope + scope.$on( + '$destroy', function () { + // Unbind keyboard handlers + angular.element($window.document.body).unbind('keydown', keydownHandler); + angular.element($window.document.body).unbind('keyup', keyupHandler); - _target = null; - isSwapped = true; - } - return true; - }; + // Unbind element drag listeners + $params.element.unbind('touchstart mousedown'); + $params.element.unbind('touchend touchcancel mouseup'); - if (angular.isFunction(targetScope.getScopeNode)) { - targetScope = targetScope.getScopeNode(); - if (!fnSwapTree()) { - return; - } - } else { - if (targetScope.$type === 'TreeDnDNodes' || targetScope.$type === 'TreeDnD') { - if (targetScope.tree_nodes) { - if (targetScope.tree_nodes.length === 0) { - if (!fnSwapTree()) { - return; - } - // Empty - isEmpty = true; - } - } else { - return; - } - } else { - return; - } + // Unbind any active document listeners (in case drag is in progress) + _fnUnbindDocumentListeners($params); + + // Cancel any pending drag timer + if ($params.dragTimer) { + $timeout.cancel($params.dragTimer); + $params.dragTimer = null; } - } - if ($params.pos.dirAx && !isSwapped || isHolder) { - isVeritcal = false; - targetScope = _info.scope; - } + // Remove and clean up drag element if still present + if ($params.dragElm) { + $params.dragElm.remove(); + $params.dragElm = null; + } - if (!targetScope.$element && !targetScope) { - return; - } + // Clean up status element + if (scope.statusElm) { + scope.statusElm.remove(); + scope.statusElm = null; + } - if (isEmpty) { - _move.parent = null; - _move.pos = 0; + // Clean up placeholder element + if (scope.placeElm) { + scope.placeElm.remove(); + scope.placeElm = null; + } - _drop = null; - } else { - // move vertical - if (isVeritcal) { - targetElm = targetScope.$element; // Get the element of tree-dnd-node - if (angular.isUndefinedOrNull(targetElm)) { - return; - } - targetOffset = $TreeDnDHelper.offset(targetElm); + // Clear dragInfo to break circular references + $params.dragInfo = null; + $params.pos = null; + $params.placeElm = null; - if (targetScope.horizontal && !targetScope.isTable) { - targetBefore = eventObj.pageX < targetOffset.left + $TreeDnDHelper.width(targetElm) / 2; - } else { - if (targetScope.isTable) { - targetBefore = eventObj.pageY < targetOffset.top + $TreeDnDHelper.height(targetElm) / 2; - } else { - var _height = $TreeDnDHelper.height(targetElm); + // Clear $params scope reference + $params.$scope = null; + $params.element = null; + } + ); + } - if (targetScope.getElementChilds()) { - _height -= -$TreeDnDHelper.height(targetScope.getElementChilds()); - } + return _$init; + } + ]); - if (eventObj.pageY > targetOffset.top + _height) { - return; - } +angular.module('ntt.TreeDnD') + .factory('$TreeDnDControl', function () { + var _target, _parent, + i, len; - targetBefore = eventObj.pageY < targetOffset.top + _height / 2; - } - } + function fnSetCollapse(node) { + node.__expanded__ = false; + } - if (!angular.isFunction(targetScope.getData)) { - return; - } + function fnSetExpand(node) { + node.__expanded__ = true; + } - _target = targetScope.getData(); - _parent = targetScope.getNode(_target.__parent_real__); + function _$init(scope) { + var n, tree = { + selected_node: null, + for_all_descendants: scope.for_all_descendants, + select_node: function (node) { + if (!node) { + if (tree.selected_node) { + delete tree.selected_node.__selected__; + } + tree.selected_node = null; + return null; + } - if (targetBefore) { - var _prev = targetScope.getPrevSibling(_target); + if (node !== tree.selected_node) { + if (tree.selected_node) { + delete tree.selected_node.__selected__; + } + node.__selected__ = true; + tree.selected_node = node; + tree.expand_all_parents(node); + if (angular.isFunction(tree.on_select)) { + tree.on_select(node); + } + } + + return node; + }, + deselect_node: function () { + _target = null; + if (tree.selected_node) { + delete tree.selected_node.__selected__; + _target = tree.selected_node; + tree.selected_node = null; + } + return _target; + }, + get_parent: function (node) { + node = node || tree.selected_node; + + if (node && node.__parent_real__ !== null) { + return scope.tree_nodes[node.__parent_real__]; + } + return null; + }, + for_all_ancestors: function (node, fn) { + _parent = tree.get_parent(node); + if (_parent) { + if (fn(_parent)) { + return false; + } + + return tree.for_all_ancestors(_parent, fn); + } + return true; + }, + expand_all_parents: function (node) { + node = node || tree.selected_node; + + if (angular.isObject(node)) { + tree.for_all_ancestors(node, fnSetExpand); + } + }, + collapse_all_parents: function (node) { + node = node || tree.selected_node; + if (angular.isObject(node)) { + tree.for_all_ancestors(node, fnSetCollapse); + } + }, + + reload_data: function () { + return scope.reload_data(); + }, + add_node: function (parent, new_node, index) { + if (typeof index !== 'number') { + if (parent) { + parent.__children__.push(new_node); + parent.__expanded__ = true; + } else { + scope.treeData.push(new_node); + } + } else { + if (parent) { + parent.__children__.splice(index, 0, new_node); + parent.__expanded__ = true; + } else { + scope.treeData.splice(index, 0, new_node); + } + } + return new_node; + }, + add_node_root: function (new_node) { + tree.add_node(null, new_node); + return new_node; + }, + expand_all: function () { + len = scope.treeData.length; + for (i = 0; i < len; i++) { + tree.for_all_descendants(scope.treeData[i], fnSetExpand); + } + }, + collapse_all: function () { + len = scope.treeData.length; + for (i = 0; i < len; i++) { + tree.for_all_descendants(scope.treeData[i], fnSetCollapse); + } + }, + remove_node: function (node) { + node = node || tree.selected_node; + + if (angular.isObject(node)) { + if (node.__parent_real__ !== null) { + _parent = tree.get_parent(node).__children__; + } else { + _parent = scope.treeData; + } + + _parent.splice(node.__index__, 1); + + tree.reload_data(); + + if (tree.selected_node === node) { + tree.selected_node = null; + } + } + }, + expand_node: function (node) { + node = node || tree.selected_node; + + if (angular.isObject(node)) { + node.__expanded__ = true; + return node; + } + }, + collapse_node: function (node) { + node = node || tree.selected_node; + + if (angular.isObject(node)) { + node.__expanded__ = false; + return node; + } + }, + get_selected_node: function () { + return tree.selected_node; + }, + get_first_node: function () { + len = scope.treeData.length; + if (len > 0) { + return scope.treeData[0]; + } + + return null; + }, + get_children: function (node) { + node = node || tree.selected_node; + + return node.__children__; + }, + get_siblings: function (node) { + node = node || tree.selected_node; + if (angular.isObject(node)) { + _parent = tree.get_parent(node); + if (_parent) { + _target = _parent.__children__; + } else { + _target = scope.treeData; + } + return _target; + } + }, + get_next_sibling: function (node) { + node = node || tree.selected_node; + if (angular.isObject(node)) { + _target = tree.get_siblings(node); + n = _target.length; + if (node.__index__ < n) { + return _target[node.__index__ + 1]; + } + } + }, + get_prev_sibling: function (node) { + node = node || tree.selected_node; + _target = tree.get_siblings(node); + if (node.__index__ > 0) { + return _target[node.__index__ - 1]; + } + }, + get_first_child: function (node) { + node = node || tree.selected_node; + if (angular.isObject(node)) { + _target = node.__children__; + if (_target && _target.length > 0) { + return node.__children__[0]; + } + } + return null; + }, + get_closest_ancestor_next_sibling: function (node) { + node = node || tree.selected_node; + _target = tree.get_next_sibling(node); + if (_target) { + return _target; + } + + _parent = tree.get_parent(node); + if (_parent) { + return tree.get_closest_ancestor_next_sibling(_parent); + } + + return null; + }, + get_next_node: function (node) { + node = node || tree.selected_node; + + if (angular.isObject(node)) { + _target = tree.get_first_child(node); + if (_target) { + return _target; + } else { + return tree.get_closest_ancestor_next_sibling(node); + } + } + }, + get_prev_node: function (node) { + node = node || tree.selected_node; + + if (angular.isObject(node)) { + _target = tree.get_prev_sibling(node); + if (_target) { + return tree.get_last_descendant(_target); + } + + _parent = tree.get_parent(node); + return _parent; + } + }, + get_last_descendant: scope.getLastDescendant, + select_parent_node: function (node) { + node = node || tree.selected_node; + + if (angular.isObject(node)) { + _parent = tree.get_parent(node); + if (_parent) { + return tree.select_node(_parent); + } + } + }, + select_first_node: function () { + var firstNode = tree.get_first_node(); + return tree.select_node(firstNode); + }, + select_next_sibling: function (node) { + node = node || tree.selected_node; + + if (angular.isObject(node)) { + _target = tree.get_next_sibling(node); + if (_target) { + return tree.select_node(_target); + } + } + }, + select_prev_sibling: function (node) { + node = node || tree.selected_node; + + if (angular.isObject(node)) { + _target = tree.get_prev_sibling(node); + if (_target) { + return tree.select_node(_target); + } + } + }, + select_next_node: function (node) { + node = node || tree.selected_node; + + if (angular.isObject(node)) { + _target = tree.get_next_node(node); + if (_target) { + return tree.select_node(_target); + } + } + }, + select_prev_node: function (node) { + node = node || tree.selected_node; + + if (angular.isObject(node)) { + _target = tree.get_prev_node(node); + if (_target) { + return tree.select_node(_target); + } + } + } + }; + angular.extend(scope.tree, tree); + return scope.tree; + } + + return _$init; + }); + +angular.module('ntt.TreeDnD') + .factory('$TreeDnDFilter', [ + '$filter', function ($filter) { + return fnInitFilter; + + function for_all_descendants(options, node, fieldChild, fnBefore, fnAfter, parentPassed) { + if (!angular.isFunction(fnBefore)) { + return null; + } - _move.parent = _parent; - _move.pos = angular.isDefined(_prev) ? _prev.__index__ + 1 : 0; + var _i, _len, _nodes, + _nodePassed = fnBefore(options, node), + _childPassed = false, + _filter_index = options.filter_index; - _drop = _prev; - } else { - if (_target.__expanded__ && !(_target.__children__.length === 1 && _target.__index_real__ === _drag.__parent_real__)) { - _move.parent = _target; - _move.pos = 0; + if (angular.isDefined(node[fieldChild])) { + _nodes = node[fieldChild]; + _len = _nodes.length; - _drop = null; - } else { - _move.parent = _parent; - _move.pos = _target.__index__ + 1; + options.filter_index = 0; + for (_i = 0; _i < _len; _i++) { + _childPassed = for_all_descendants( + options, + _nodes[_i], + fieldChild, + fnBefore, + fnAfter, + _nodePassed || parentPassed + ) || _childPassed; + } - _drop = _target; - } - } - } else { - // move horizontal - if ($params.pos.dirAx && $params.pos.distAxX >= treeScope.dragBorder) { - $params.pos.distAxX = 0; - // increase horizontal level if previous sibling exists and is not collapsed - if ($params.pos.distX > 0) { - _parent = _drop; - if (!_parent) { - if (_move.pos - 1 >= 0) { - _parent = _move.parent.__children__[_move.pos - 1]; - } else { - return; - } - } + // restore filter_index of node + options.filter_index = _filter_index; + } - if (_info.drag === _info.target && _parent === _drag && _$scope.enabledMove) { - _parent = treeScope.getPrevSibling(_parent); - } + if (angular.isFunction(fnAfter)) { + fnAfter(options, node, _nodePassed === true, _childPassed === true, parentPassed === true); + } - if (_parent && _parent.__visible__) { - var _len = _parent.__children__.length; + return _nodePassed || _childPassed; + } - _move.parent = _parent; - _move.pos = _len; + /** + * Check data with callback + * @param {string|object|function|regex} callback + * @param {*} data + * @returns {null|boolean} + * @private + */ + function _fnCheck(callback, data) { + if (angular.isUndefinedOrNull(data) || angular.isArray(data)) { + return null; + } - if (_len > 0) { - _drop = _parent.__children__[_len - 1]; - } else { - _drop = null; - } - } else { - // Not changed - return; - } - } else if ($params.pos.distX < 0) { - _target = _move.parent; - if (_target && - (_target.__children__.length === 0 || - _target.__children__.length - 1 < _move.pos || - _info.drag === _info.target && - _target.__index_real__ === _drag.__parent_real__ && - _target.__children__.length - 1 === _drag.__index__ && _$scope.enabledMove) - ) { - _parent = treeScope.getNode(_target.__parent_real__); + if (angular.isFunction(callback)) { + return callback(data, $filter); + } else { + if (typeof callback === 'boolean') { + data = !!data; + return data === callback; + } else if (angular.isDefined(callback)) { + try { + var _regex = new RegExp(callback); + return _regex.test(data); + } + catch (err) { + if (typeof data === 'string') { + return data.indexOf(callback) > -1; + } else { + return null; + } + } + } else { + return null; + } + } + } - _move.parent = _parent; - _move.pos = _target.__index__ + 1; + /** + * `fnProcess` to call `_fnCheck`. If `condition` is `array` then call `for_each_filter` + * else will call `_fnCheck`. Specical `condition.field` is `_$` then apply `condition.callback` for all field, if have `field` invaild then `return true`. + * + * @param node + * @param condition + * @param isAnd + * @returns {null|boolean} + * @private + */ + function _fnProccess(node, condition, isAnd) { + if (angular.isArray(condition)) { + return for_each_filter(node, condition, isAnd); + } else { + var _key = condition.field, + _callback = condition.callback, + _iO, _keysO, _lenO; - _drop = _target; - } else { - // Not changed - return; - } - } else { - return; - } - } else { - // limited - return; + if (_key === '_$') { + _keysO = Object.keys(node); + _lenO = _keysO.length; + for (_iO = 0; _iO < _lenO; _iO++) { + if (_fnCheck(_callback, node[_keysO[_iO]])) { + return true; } } + } else if (angular.isDefined(node[_key])) { + return _fnCheck(_callback, node[_key]); } + } + return null; + } - if (_info.drag === _info.target && - _move.parent && - _drag.__parent_real__ === _move.parent.__index_real__ && - _drag.__index__ === _move.pos - ) { - isChanged = false; + /** + * + * @param {object} node + * @param {array} conditions Array `conditions` + * @param {boolean} isAnd check with condition `And`, if `And` then `return false` when all `false` + * @returns {null|boolean} + */ + function for_each_filter(node, conditions, isAnd) { + var i, len = conditions.length || 0, passed = false; + if (len === 0) { + return null; + } + + for (i = 0; i < len; i++) { + if (_fnProccess(node, conditions[i], !isAnd)) { + passed = true; + // if condition `or` then return; + if (!isAnd) { + return true; + } + } else { + + // if condition `and` and result in fnProccess = false then return; + if (isAnd) { + return false; + } } + } - if (treeScope.$callbacks.accept(_info, _move, isChanged)) { - _info.move = _move; - _info.drop = _drop; - _info.changed = isChanged; - _info.scope = targetScope; + return passed; + } - if (targetScope.isTable) { - $TreeDnDHelper.replaceIndent( - treeScope, - $params.placeElm, - angular.isUndefinedOrNull(_move.parent) ? 1 : _move.parent.__level__ + 1 - ); + /** + * Will call _fnAfter to clear data no need + * @param {object} options + * @param {object} node + * @param {boolean} isNodePassed + * @param {boolean} isChildPassed + * @param {boolean} isParentPassed + * @private + */ + function _fnAfter(options, node, isNodePassed, isChildPassed, isParentPassed) { + if (isNodePassed === true) { + node.__filtered__ = true; + node.__filtered_visible__ = true; + node.__filtered_index__ = options.filter_index++; + return; //jmp + } else if (isChildPassed === true && options.showParent === true + || isParentPassed === true && options.showChild === true) { + node.__filtered__ = false; + node.__filtered_visible__ = true; + node.__filtered_index__ = options.filter_index++; + return; //jmp + } + + // remove attr __filtered__ + delete node.__filtered__; + delete node.__filtered_visible__; + delete node.__filtered_index__; + } + + /** + * `fnBefore` will called when `for_all_descendants` of `node` checking. + * If `filter` empty then return `true` else result of function `_fnProccess` {@see _fnProccess} + * + * @param {object} options + * @param {object} node + * @returns {null|boolean} + * @private + */ + function _fnBefore(options, node) { + if (options.filter.length === 0) { + return true; + } else { + return _fnProccess(node, options.filter, options.beginAnd || false); + } + } + + /** + * `fnBeforeClear` will called when `for_all_descendants` of `node` checking. + * Alway false to Clear Filter empty + * + * @param {object} options + * @param {object} node + * @returns {null|boolean} + * @private + */ + function _fnBeforeClear(options, node) { + return false; + } - if (_drop) { - _parent = (_move.parent ? _move.parent.__children__ : null) || _info.target.treeData; + /** + * `_fnConvert` to convert `filter` `object` to `array` invaild. + * + * @param {object|array} filters + * @returns {array} Instead of `filter` or new array invaild *(converted from filter)* + * @private + */ + function _fnConvert(filters) { + var _iF, _lenF, _keysF, + _filter, + _state; + // convert filter object to array filter + if (angular.isObject(filters) && !angular.isArray(filters)) { + _keysF = Object.keys(filters); + _lenF = _keysF.length; + _filter = []; - if (_drop.__index__ < _parent.length - 1) { - // Find fast - _drop = _parent[_drop.__index__ + 1]; - _scope = _info.target.getScope(_drop); - _scope.$element[0].parentNode.insertBefore( - $params.placeElm[0], - _scope.$element[0] - ); - } else { - _target = _info.target.getLastDescendant(_drop); - _scope = _info.target.getScope(_target); - _scope.$element.after($params.placeElm); - } - } else { - _scope = _info.target.getScope(_move.parent); - if (_scope) { - if (_move.parent) { - _scope.$element.after($params.placeElm); + if (_lenF > 0) { + for (_iF = 0; _iF < _lenF; _iF++) { - } else { - _scope.getElementChilds().prepend($params.placeElm); - } - } - } - } else { - _scope = _info.target.getScope(_drop || _move.parent); - if (_drop) { - _scope.$element.after($params.placeElm); + if (typeof filters[_keysF[_iF]] === 'string' && filters[_keysF[_iF]].length === 0) { + continue; + } else if (angular.isArray(filters[_keysF[_iF]])) { + _state = filters[_keysF[_iF]]; + } else if (angular.isObject(filters[_keysF[_iF]])) { + _state = _fnConvert(filters[_keysF[_iF]]); } else { - _scope.getElementChilds().prepend($params.placeElm); + _state = { + field: _keysF[_iF], + callback: filters[_keysF[_iF]] + }; } + _filter.push(_state); } + } + _state = null; + return _filter; + } + else { + return filters; + } + } - treeScope.showPlace(); + /** + * `fnInitFilter` function is constructor of service `$TreeDnDFilter`. + * @constructor + * @param {object|array} treeData + * @param {object|array} filters + * @param {object} options + * @param {string} keyChild + * @returns {array} Return `treeData` or `treeData` with `filter` + * @private + */ + function fnInitFilter(treeData, filters, options, keyChild) { + if (!angular.isArray(treeData) + || treeData.length === 0) { + return treeData; + } - _$scope.$safeApply( - function () { - _$scope.$callbacks.dragMove(_info); - } + var _i, _len, + _filter; + + _filter = _fnConvert(filters); + if (!(angular.isArray(_filter) || angular.isObject(_filter)) + || _filter.length === 0) { + for (_i = 0, _len = treeData.length; _i < _len; _i++) { + for_all_descendants( + options, + treeData[_i], + keyChild || '__children__', + _fnBeforeClear, _fnAfter ); } + return treeData; + } + options.filter = _filter; + options.filter_index = 0; + for (_i = 0, _len = treeData.length; _i < _len; _i++) { + for_all_descendants( + options, + treeData[_i], + keyChild || '__children__', + _fnBefore, _fnAfter + ); } + + return treeData; } - function _fnDragEnd(e, $params) { - e.preventDefault(); - if ($params.dragElm) { - var _passed = false, - _$scope = $params.$scope, - _scope = _$scope.getScope($params.dragInfo.node), - _element = _scope.$element; + }] + ); - _$scope.$safeApply( - function () { - _passed = _$scope.$callbacks.beforeDrop($params.dragInfo); - } - ); +angular.module('ntt.TreeDnD') + .factory('$TreeDnDOrderBy', [ + '$filter', + function ($filter) { + var _fnOrderBy = $filter('orderBy'), + for_all_descendants = function for_all_descendants(options, node, name, fnOrderBy) { + var _i, _len, _nodes; - // rollback all - if (_scope.isTable) { - _$scope.for_all_descendants( - $params.dragInfo.node, function (_node, _parent) { - _scope = _$scope.getScope(_node); - _element = _scope && _scope.$element; - if (_scope && _element && (!_parent && _node.__visible__ || _parent.__expanded__)) { - if (_$scope.$class.hidden) { - _element.removeClass(_$scope.$class.hidden); - } - } - return _node.__visible__ === false || _node.__expanded__ === false - }, null, true - ); - } else { - if (_$scope.$class.hidden) { - _element.removeClass(_$scope.$class.hidden); + if (angular.isDefined(node[name])) { + _nodes = node[name]; + _len = _nodes.length; + // OrderBy children + for (_i = 0; _i < _len; _i++) { + _nodes[_i] = for_all_descendants(options, _nodes[_i], name, fnOrderBy); } + + node[name] = fnOrderBy(node[name], options); + } + return node; + }, + _fnOrder = function _fnOrder(list, orderBy) { + return _fnOrderBy(list, orderBy); + }, + _fnMain = function _fnMain(treeData, orderBy) { + if (!angular.isArray(treeData) + || treeData.length === 0 + || !(angular.isArray(orderBy) || angular.isObject(orderBy) || angular.isString(orderBy) || angular.isFunction(orderBy)) + || orderBy.length === 0 && !angular.isFunction(orderBy)) { + return treeData; } - $params.dragElm.remove(); - $params.dragElm = null; + var _i, _len; - if (_$scope.enabledStatus) { - _$scope.hideStatus(); + for (_i = 0, _len = treeData.length; _i < _len; _i++) { + treeData[_i] = for_all_descendants( + orderBy, + treeData[_i], + '__children__', + _fnOrder + ); } - if (_$scope.$$apply) { - _$scope.$safeApply( - function () { - var _status = _$scope.$callbacks.dropped( - $params.dragInfo, - _passed - ); + return _fnOrder(treeData, orderBy); + }; - _$scope.$callbacks.dragStop($params.dragInfo, _status); - clearData(); + return _fnMain; + }] + ); + +angular.module('ntt.TreeDnD') + .factory('$TreeDnDConvert', function () { + var _$initConvert = { + line2tree: function (data, primaryKey, parentKey, callback) { + callback = typeof callback === 'function' ? callback : function () { + }; + if (!data || data.length === 0 || !primaryKey || !parentKey) { + return []; + } + var tree = [], + rootIds = [], + item = data[0], + _primary = item[primaryKey], + treeObjs = {}, + parentId, parent, + len = data.length, + i = 0; + + while (i < len) { + item = data[i++]; + callback(item); + _primary = item[primaryKey]; + treeObjs[_primary] = item; + } + i = 0; + while (i < len) { + item = data[i++]; + callback(item); + _primary = item[primaryKey]; + treeObjs[_primary] = item; + parentId = item[parentKey]; + if (parentId) { + parent = treeObjs[parentId]; + if (parent) { + if (parent.__children__) { + parent.__children__.push(item); + } else { + parent.__children__ = [item]; } - ); + } } else { - _fnBindDrag($params); - _$scope.$safeApply( - function () { - _$scope.$callbacks.dragStop($params.dragInfo, false); - clearData(); - } - ); + rootIds.push(_primary); } - } - - function clearData() { - $params.dragInfo.target.hidePlace(); - $params.dragInfo.target.targeting = false; - - $params.dragInfo = null; - _$scope.$$apply = false; - _$scope.setDragging(null); + len = rootIds.length; + for (i = 0; i < len; i++) { + tree.push(treeObjs[rootIds[i]]); } - - angular.element($params.$document).unbind('touchend', $params.dragEndEvent); // Mobile - angular.element($params.$document).unbind('touchcancel', $params.dragEndEvent); // Mobile - angular.element($params.$document).unbind('touchmove', $params.dragMoveEvent); // Mobile - angular.element($params.$document).unbind('mouseup', $params.dragEndEvent); - angular.element($params.$document).unbind('mousemove', $params.dragMoveEvent); - angular.element($params.$window.document.body).unbind('mouseleave', $params.dragCancelEvent); - } - - function _fnDragStartEvent(e, $params) { - if ($params.$scope.$callbacks.draggable()) { - _fnDragStart(e, $params); + return tree; + }, + tree2tree: function access_child(data, containKey, callback) { + callback = typeof callback === 'function' ? callback : function () { + }; + var _tree = [], + _i, + _len = data ? data.length : 0, + _copy, _child; + for (_i = 0; _i < _len; _i++) { + _copy = angular.copy(data[_i]); + callback(_copy); + if (angular.isArray(_copy[containKey]) && _copy[containKey].length > 0) { + _child = access_child(_copy[containKey], containKey, callback); + delete _copy[containKey]; + _copy.__children__ = _child; + } + _tree.push(_copy); } + return _tree; } + }; - function _fnBindDrag($params) { - $params.element.bind( - 'touchstart mousedown', function (e) { - $params.dragDelaying = true; - $params.dragStarted = false; - _fnDragStartEvent(e, $params); - $params.dragTimer = $timeout( - function () { - $params.dragDelaying = false; - }, $params.$scope.dragDelay - ); - } - ); - - $params.element.bind( - 'touchend touchcancel mouseup', function () { - $timeout.cancel($params.dragTimer); - } - ); - } + return _$initConvert; + }); - function _fnKeydownHandler(e, $params) { - var _$scope = $params.$scope; - if (e.keyCode === 27) { - if (_$scope.enabledStatus) { - _$scope.hideStatus(); +angular.module('ntt.TreeDnD') + .factory('$TreeDnDHelper', [ + '$document', '$window', + function ($document, $window) { + var _$helper = { + nodrag: function (targetElm) { + return typeof targetElm.attr('data-nodrag') !== 'undefined'; + }, + eventObj: function (e) { + var obj = e; + if (e.targetTouches !== undefined) { + obj = e.targetTouches.item(0); + } else if (e.originalEvent !== undefined && e.originalEvent.targetTouches !== undefined) { + obj = e.originalEvent.targetTouches.item(0); } + return obj; + }, + dragInfo: function (scope) { + var _node = scope.getData(), + _tree = scope.getScopeTree(), + _parent = scope.getNode(_node.__parent_real__); - _$scope.$$apply = false; - _fnDragEnd(e, $params); - } else { - if (_$scope.enabledHotkey && e.shiftKey) { - _$scope.enableMove(true); - if (_$scope.enabledStatus) { - _$scope.refreshStatus(); - } - - if (!$params.dragInfo) { - return; - } + return { + node: _node, + parent: _parent, + move: { + parent: _parent, + pos: _node.__index__ + }, + scope: scope, + target: _tree, + drag: _tree, + drop: scope.getPrevSibling(_node), + changed: false + }; + }, + height: function (element) { + return element.prop('scrollHeight'); + }, + width: function (element) { + return element.prop('scrollWidth'); + }, + offset: function (element) { + var boundingClientRect = element[0].getBoundingClientRect(); + return { + width: element.prop('offsetWidth'), + height: element.prop('offsetHeight'), + top: boundingClientRect.top + ($window.pageYOffset || $document[0].body.scrollTop || $document[0].documentElement.scrollTop), + left: boundingClientRect.left + ($window.pageXOffset || $document[0].body.scrollLeft || $document[0].documentElement.scrollLeft) + }; + }, + positionStarted: function (e, target) { + return { + offsetX: e.pageX - this.offset(target).left, + offsetY: e.pageY - this.offset(target).top, + startX: e.pageX, + lastX: e.pageX, + startY: e.pageY, + lastY: e.pageY, + nowX: 0, + nowY: 0, + distX: 0, + distY: 0, + dirAx: 0, + dirX: 0, + dirY: 0, + lastDirX: 0, + lastDirY: 0, + distAxX: 0, + distAxY: 0 + }; + }, + positionMoved: function (e, pos, firstMoving) { + // mouse position last events + pos.lastX = pos.nowX; + pos.lastY = pos.nowY; - var _scope = _$scope.getScope($params.dragInfo.node), - _element = _scope.$element; + // mouse position this events + pos.nowX = e.pageX; + pos.nowY = e.pageY; - if (_scope.isTable) { - _$scope.for_all_descendants( - $params.dragInfo.node, function (_node, _parent) { - _scope = _$scope.getScope(_node); - _element = _scope && _scope.$element; - if (_scope && _element && (!_parent && _node.__visible__ || _parent.__expanded__)) { - if (_$scope.$class.hidden) { - _element.addClass(_$scope.$class.hidden); - } - } - return _node.__visible__ === false || _node.__expanded__ === false + // distance mouse moved between events + pos.distX = pos.nowX - pos.lastX; + pos.distY = pos.nowY - pos.lastY; - }, null, true - ); - } else { - if (_$scope.$class.hidden) { - _element.addClass(_$scope.$class.hidden); - } - } - } - } - } + // direction mouse was moving + pos.lastDirX = pos.dirX; + pos.lastDirY = pos.dirY; - function _fnKeyupHandler(e, $params) { - var _$scope = $params.$scope; - if (_$scope.enabledHotkey && !e.shiftKey) { - _$scope.enableMove(false); + // direction mouse is now moving (on both axis) + pos.dirX = pos.distX === 0 ? 0 : pos.distX > 0 ? 1 : -1; + pos.dirY = pos.distY === 0 ? 0 : pos.distY > 0 ? 1 : -1; - if (_$scope.enabledStatus) { - _$scope.refreshStatus(); - } + // axis mouse is now moving on + var newAx = Math.abs(pos.distX) > Math.abs(pos.distY) ? 1 : 0; - if (!$params.dragInfo) { + // do nothing on first move + if (firstMoving) { + pos.dirAx = newAx; + pos.moving = true; return; } - var _scope = _$scope.getScope($params.dragInfo.node), - _element = _scope.$element; - - if (_scope.isTable) { - _$scope.for_all_descendants( - $params.dragInfo.node, function (_node, _parent) { - _scope = _$scope.getScope(_node); - _element = _scope && _scope.$element; - if (_scope && _element && (!_parent && _node.__visible__ || _parent.__expanded__)) { - if (_$scope.$class.hidden) { - _element.removeClass(_$scope.$class.hidden); - } - } - return _node.__visible__ === false || _node.__expanded__ === false - }, null, true - ); + // calc distance moved on this axis (and direction) + if (pos.dirAx !== newAx) { + pos.distAxX = 0; + pos.distAxY = 0; } else { - if (_$scope.$class.hidden) { - _element.removeClass(_$scope.$class.hidden); + pos.distAxX += Math.abs(pos.distX); + if (pos.dirX !== 0 && pos.dirX !== pos.lastDirX) { + pos.distAxX = 0; } - } - } - } - - function _$init(scope, element, $window, $document) { - var $params = { - hasTouch: 'ontouchstart' in window, - firstMoving: null, - dragInfo: null, - pos: null, - placeElm: null, - dragElm: null, - dragDelaying: true, - dragStarted: false, - dragTimer: null, - body: document.body, - html: document.documentElement, - document_height: null, - document_width: null, - offsetEdge: null, - $scope: scope, - $window: $window, - $document: $document, - element: element, - bindDrag: function () { - _fnBindDrag($params); - }, - dragEnd: function (e) { - _fnDragEnd(e, $params); - }, - dragMoveEvent: function (e) { - _fnDragMove(e, $params); - }, - dragEndEvent: function (e) { - scope.$$apply = true; - _fnDragEnd(e, $params); - }, - dragCancelEvent: function (e) { - _fnDragEnd(e, $params); + pos.distAxY += Math.abs(pos.distY); + if (pos.dirY !== 0 && pos.dirY !== pos.lastDirY) { + pos.distAxY = 0; } - }, - keydownHandler = function (e) { - return _fnKeydownHandler(e, $params); - }, - keyupHandler = function (e) { - return _fnKeyupHandler(e, $params); - }; + } + pos.dirAx = newAx; + }, + replaceIndent: function (scope, element, indent, attr) { + attr = attr || 'left'; + angular.element(element.children()[0]).css(attr, scope.$callbacks.calsIndent(indent)); + } + }; - scope.dragEnd = function (e) { - $params.dragEnd(e); - }; + return _$helper; + }] + ); - $params.bindDrag(); +angular.module('ntt.TreeDnD') + .factory('$TreeDnDPlugin', [ + '$injector', + function ($injector) { + var _fnget = function (name) { + if (angular.isDefined($injector) && $injector.has(name)) { + return $injector.get(name); + } + return null; + }; + return _fnget; + }] + ); - angular.element($window.document.body).bind('keydown', keydownHandler); - angular.element($window.document.body).bind('keyup', keyupHandler); - //unbind handler that retains scope - scope.$on( - '$destroy', function () { - angular.element($window.document.body).unbind('keydown', keydownHandler); - angular.element($window.document.body).unbind('keyup', keyupHandler); - if (scope.statusElm) { - scope.statusElm.remove(); +angular.module('ntt.TreeDnD') + .factory('$TreeDnDTemplate', [ + '$templateCache', + function ($templateCache) { + var templatePath = 'template/TreeDnD/TreeDnD.html', + copyPath = 'template/TreeDnD/TreeDnDStatusCopy.html', + movePath = 'template/TreeDnD/TreeDnDStatusMove.html', + scopes = {}, + temp, + _$init = { + setMove: function (path, scope) { + if (!scopes[scope.$id]) { + scopes[scope.$id] = {}; } - - if (scope.placeElm) { - scope.placeElm.remove(); + scopes[scope.$id].movePath = path; + }, + setCopy: function (path, scope) { + if (!scopes[scope.$id]) { + scopes[scope.$id] = {}; + } + scopes[scope.$id].copyPath = path; + }, + getPath: function () { + return templatePath; + }, + getCopy: function (scope) { + if (scopes[scope.$id] && scopes[scope.$id].copyPath) { + temp = $templateCache.get(scopes[scope.$id].copyPath); + if (temp) { + return temp; + } + } + return $templateCache.get(copyPath); + }, + getMove: function (scope) { + if (scopes[scope.$id] && scopes[scope.$id].movePath) { + temp = $templateCache.get(scopes[scope.$id].movePath); + if (temp) { + return temp; + } } + return $templateCache.get(movePath); } - ); - } + }; return _$init; - } - ]); + }] + ); angular.module('ntt.TreeDnD') - .factory('$TreeDnDControl', function () { - var _target, _parent, - i, len; + .factory('$TreeDnDViewport', fnInitTreeDnDViewport); - function fnSetCollapse(node) { - node.__expanded__ = false; - } +fnInitTreeDnDViewport.$inject = ['$window', '$document', '$timeout', '$q', '$compile']; - function fnSetExpand(node) { - node.__expanded__ = true; - } +function fnInitTreeDnDViewport($window, $document, $timeout, $q, $compile) { - function _$init(scope) { - var n, tree = { - selected_node: null, - for_all_descendants: scope.for_all_descendants, - select_node: function (node) { - if (!node) { - if (tree.selected_node) { - delete tree.selected_node.__selected__; - } - tree.selected_node = null; - return null; - } + var viewport = null, + isUpdating = false, + isRender = false, + updateAgain = false, + viewportRect, + items = [], + nodeTemplate, + updateTimeout, + renderTime, + windowListenersBound = false, + $initViewport = { + setViewport: setViewport, + getViewport: getViewport, + add: add, + remove: remove, + setTemplate: setTemplate, + getItems: getItems, + updateDelayed: updateDelayed, + destroy: destroy + }, + eWindow = angular.element($window); - if (node !== tree.selected_node) { - if (tree.selected_node) { - delete tree.selected_node.__selected__; - } - node.__selected__ = true; - tree.selected_node = node; - tree.expand_all_parents(node); - if (angular.isFunction(tree.on_select)) { - tree.on_select(node); - } - } + return $initViewport; - return node; - }, - deselect_node: function () { - _target = null; - if (tree.selected_node) { - delete tree.selected_node.__selected__; - _target = tree.selected_node; - tree.selected_node = null; - } - return _target; - }, - get_parent: function (node) { - node = node || tree.selected_node; + /** + * Bind window event listeners (lazily on first add) + */ + function bindWindowListeners() { + if (!windowListenersBound) { + eWindow.on('load resize scroll', updateDelayed); + windowListenersBound = true; + } + } - if (node && node.__parent_real__ !== null) { - return scope.tree_nodes[node.__parent_real__]; - } - return null; - }, - for_all_ancestors: function (node, fn) { - _parent = tree.get_parent(node); - if (_parent) { - if (fn(_parent)) { - return false; - } + /** + * Unbind window event listeners and clean up resources + */ + function destroy() { + if (windowListenersBound) { + eWindow.off('load resize scroll', updateDelayed); + windowListenersBound = false; + } - return tree.for_all_ancestors(_parent, fn); - } - return true; - }, - expand_all_parents: function (node) { - node = node || tree.selected_node; + // Cancel any pending timeouts + if (updateTimeout) { + $timeout.cancel(updateTimeout); + updateTimeout = null; + } + if (renderTime) { + $timeout.cancel(renderTime); + renderTime = null; + } - if (angular.isObject(node)) { - tree.for_all_ancestors(node, fnSetExpand); - } - }, - collapse_all_parents: function (node) { - node = node || tree.selected_node; - if (angular.isObject(node)) { - tree.for_all_ancestors(node, fnSetCollapse); - } - }, + // Clear all item references + items.length = 0; + viewport = null; + nodeTemplate = null; + isUpdating = false; + isRender = false; + updateAgain = false; + } - reload_data: function () { - return scope.reload_data(); - }, - add_node: function (parent, new_node, index) { - if (typeof index !== 'number') { - if (parent) { - parent.__children__.push(new_node); - parent.__expanded__ = true; - } else { - scope.treeData.push(new_node); - } - } else { - if (parent) { - parent.__children__.splice(index, 0, new_node); - parent.__expanded__ = true; - } else { - scope.treeData.splice(index, 0, new_node); - } - } - return new_node; - }, - add_node_root: function (new_node) { - tree.add_node(null, new_node); - return new_node; - }, - expand_all: function () { - len = scope.treeData.length; - for (i = 0; i < len; i++) { - tree.for_all_descendants(scope.treeData[i], fnSetExpand); - } - }, - collapse_all: function () { - len = scope.treeData.length; - for (i = 0; i < len; i++) { - tree.for_all_descendants(scope.treeData[i], fnSetCollapse); - } - }, - remove_node: function (node) { - node = node || tree.selected_node; + function update() { - if (angular.isObject(node)) { - if (node.__parent_real__ !== null) { - _parent = tree.get_parent(node).__children__; - } else { - _parent = scope.treeData; - } + viewportRect = { + width: eWindow.prop('offsetWidth') || document.documentElement.clientWidth, + height: eWindow.prop('offsetHeight') || document.documentElement.clientHeight, + top: $document[0].body.scrollTop || $document[0].documentElement.scrollTop, + left: $document[0].body.scrollLeft || $document[0].documentElement.scrollLeft + }; - _parent.splice(node.__index__, 1); + if (isUpdating || isRender) { + updateAgain = true; + return; + } + isUpdating = true; - tree.reload_data(); + recursivePromise(); + } - if (tree.selected_node === node) { - tree.selected_node = null; - } - } - }, - expand_node: function (node) { - node = node || tree.selected_node; + function recursivePromise() { + if (isRender) { + return; + } - if (angular.isObject(node)) { - node.__expanded__ = true; - return node; - } - }, - collapse_node: function (node) { - node = node || tree.selected_node; + var number = number > 0 ? number : items.length, item; - if (angular.isObject(node)) { - node.__expanded__ = false; - return node; - } - }, - get_selected_node: function () { - return tree.selected_node; - }, - get_first_node: function () { - len = scope.treeData.length; - if (len > 0) { - return scope.treeData[0]; - } + if (number > 0) { + item = items[0]; - return null; - }, - get_children: function (node) { - node = node || tree.selected_node; + isRender = true; + renderTime = $timeout(function () { + //item.element.html(nodeTemplate); + //$compile(item.element.contents())(item.scope); - return node.__children__; - }, - get_siblings: function (node) { - node = node || tree.selected_node; - if (angular.isObject(node)) { - _parent = tree.get_parent(node); - if (_parent) { - _target = _parent.__children__; - } else { - _target = scope.treeData; - } - return _target; - } - }, - get_next_sibling: function (node) { - node = node || tree.selected_node; - if (angular.isObject(node)) { - _target = tree.get_siblings(node); - n = _target.length; - if (node.__index__ < n) { - return _target[node.__index__ + 1]; - } - } - }, - get_prev_sibling: function (node) { - node = node || tree.selected_node; - _target = tree.get_siblings(node); - if (node.__index__ > 0) { - return _target[node.__index__ - 1]; - } - }, - get_first_child: function (node) { - node = node || tree.selected_node; - if (angular.isObject(node)) { - _target = node.__children__; - if (_target && _target.length > 0) { - return node.__children__[0]; - } - } - return null; - }, - get_closest_ancestor_next_sibling: function (node) { - node = node || tree.selected_node; - _target = tree.get_next_sibling(node); - if (_target) { - return _target; - } + items.splice(0, 1); + isRender = false; + number--; + $timeout.cancel(renderTime); + recursivePromise(); + }, 0); - _parent = tree.get_parent(node); - if (_parent) { - return tree.get_closest_ancestor_next_sibling(_parent); - } + } else { + isUpdating = false; + if (updateAgain) { + updateAgain = false; + update(); + } + } - return null; - }, - get_next_node: function (node) { - node = node || tree.selected_node; + } - if (angular.isObject(node)) { - _target = tree.get_first_child(node); - if (_target) { - return _target; - } else { - return tree.get_closest_ancestor_next_sibling(node); - } - } - }, - get_prev_node: function (node) { - node = node || tree.selected_node; + /** + * Check if a point is inside specified bounds + * @param x + * @param y + * @param bounds + * @returns {boolean} + */ + function pointIsInsideBounds(x, y, bounds) { + return x >= bounds.left && + y >= bounds.top && + x <= bounds.left + bounds.width && + y <= bounds.top + bounds.height; + } - if (angular.isObject(node)) { - _target = tree.get_prev_sibling(node); - if (_target) { - return tree.get_last_descendant(_target); - } + /** + * @name setViewport + * @desciption Set the viewport element + * @param element + */ + function setViewport(element) { + viewport = element; + } - _parent = tree.get_parent(node); - return _parent; - } - }, - get_last_descendant: scope.getLastDescendant, - select_parent_node: function (node) { - node = node || tree.selected_node; + /** + * Return the current viewport + * @returns {*} + */ + function getViewport() { + return viewport; + } - if (angular.isObject(node)) { - _parent = tree.get_parent(node); - if (_parent) { - return tree.select_node(_parent); - } - } - }, - select_first_node: function () { - var firstNode = tree.get_first_node(); - return tree.select_node(firstNode); - }, - select_next_sibling: function (node) { - node = node || tree.selected_node; + /** + * trigger an update + */ + function updateDelayed() { + $timeout.cancel(updateTimeout); + updateTimeout = $timeout(function () { + update(); + }, 0); + } - if (angular.isObject(node)) { - _target = tree.get_next_sibling(node); - if (_target) { - return tree.select_node(_target); - } - } - }, - select_prev_sibling: function (node) { - node = node || tree.selected_node; + /** + * Add listener for event + * @param element + * @param callback + */ + function add(scope, element) { + // Lazily bind window listeners on first add + bindWindowListeners(); + updateDelayed(); + items.push({ + element: element, + scope: scope + }); + } - if (angular.isObject(node)) { - _target = tree.get_prev_sibling(node); - if (_target) { - return tree.select_node(_target); - } - } - }, - select_next_node: function (node) { - node = node || tree.selected_node; + function remove(scope, element) { + var i = items.length; + while (i--) { + if (items[i].scope === scope || (element && items[i].element === element)) { + // Clear references before removing + items[i].scope = null; + items[i].element = null; + items.splice(i, 1); + } + } + // Auto-cleanup: unbind window listeners when no items remain + if (items.length === 0 && windowListenersBound) { + eWindow.off('load resize scroll', updateDelayed); + windowListenersBound = false; + } + } - if (angular.isObject(node)) { - _target = tree.get_next_node(node); - if (_target) { - return tree.select_node(_target); - } - } - }, - select_prev_node: function (node) { - node = node || tree.selected_node; + function setTemplate(scope, template) { + nodeTemplate = template; + } - if (angular.isObject(node)) { - _target = tree.get_prev_node(node); - if (_target) { - return tree.select_node(_target); - } - } - } - }; - angular.extend(scope.tree, tree); - return scope.tree; - } + /** + * Get list of items + * @returns {Array} + */ + function getItems() { + return items; + } +} - return _$init; - }); angular.module('template/TreeDnD/TreeDnD.html', []).run( ['$templateCache', function ($templateCache) { @@ -3365,8 +3654,8 @@ angular.module('template/TreeDnD/TreeDnD.html', []).run( ' ', ' ', ' ', - ' ', ' 0 || nodeOf.__has_children__ === true || nodeOf.__lazy__ === true, _i; if (!nodeOf.__inited__) { @@ -221,7 +267,7 @@ angular.module('ntt.TreeDnD') nodeOf.__visible__ = true; } - if (_len === 0) { + if (!_hasChilds) { _icon = -1; } else { if (nodeOf.__expanded__) { @@ -259,6 +305,7 @@ angular.module('ntt.TreeDnD') }] ); + angular.module('ntt.TreeDnD') .directive('treeDndNodes', function () { return { @@ -282,11 +329,11 @@ angular.module('ntt.TreeDnD') 'treeDnd', fnInitTreeDnD); fnInitTreeDnD.$inject = [ - '$timeout', '$http', '$compile', '$parse', '$window', '$document', '$templateCache', + '$timeout', '$http', '$compile', '$parse', '$window', '$document', '$templateCache', '$q', '$TreeDnDTemplate', '$TreeDnDClass', '$TreeDnDHelper', '$TreeDnDPlugin', '$TreeDnDViewport' ]; -function fnInitTreeDnD($timeout, $http, $compile, $parse, $window, $document, $templateCache, +function fnInitTreeDnD($timeout, $http, $compile, $parse, $window, $document, $templateCache, $q, $TreeDnDTemplate, $TreeDnDClass, $TreeDnDHelper, $TreeDnDPlugin, $TreeDnDViewport) { return { restrict: 'E', @@ -401,8 +448,18 @@ function fnInitTreeDnD($timeout, $http, $compile, $parse, $window, $document, $t } if (passedExpand) { + if (node.__expanded__) { + node.__expanded__ = false; + return; + } + if (node.__children__.length > 0) { - node.__expanded__ = !node.__expanded__; + node.__expanded__ = true; + return; + } + + if (nodeHasChildren(node) && angular.isFunction($scope.$callbacks.loadChildren)) { + $scope.loadChildren(node); } } }; @@ -471,6 +528,9 @@ function fnInitTreeDnD($timeout, $http, $compile, $parse, $window, $document, $t this.for_all_descendants(_clone, this.changeKey); return _clone; }, + loadChildren: function () { + return null; + }, remove: function (node, parent, _this, delayReload) { var temp = parent.splice(node.__index__, 1)[0]; if (!delayReload) { @@ -481,6 +541,13 @@ function fnInitTreeDnD($timeout, $http, $compile, $parse, $window, $document, $t clearInfo: function (node) { delete node.__inited__; delete node.__visible__; + delete node.__icon__; + delete node.__icon_class__; + delete node.__level__; + delete node.__index__; + delete node.__index_real__; + delete node.__parent_real__; + delete node.__dept__; // always changed after call reload_data //delete node.__hashKey__; @@ -526,6 +593,38 @@ function fnInitTreeDnD($timeout, $http, $compile, $parse, $window, $document, $t return $scope; }; + function nodeHasChildren(node) { + return (angular.isArray(node.__children__) && node.__children__.length > 0) || + node.__has_children__ === true || + node.__lazy__ === true; + } + + $scope.loadChildren = function (node) { + if (!node || node.__loading__) { + return $q.when([]); + } + + node.__loading__ = true; + + return $q.when($scope.$callbacks.loadChildren(node)).then( + function (children) { + if (angular.isArray(children)) { + node.__children__ = children; + } else if (!angular.isArray(node.__children__)) { + node.__children__ = []; + } + + node.__lazy__ = false; + node.__has_children__ = node.__children__.length > 0; + node.__expanded__ = node.__children__.length > 0; + reload_data(); + return node.__children__; + } + ).finally(function () { + node.__loading__ = false; + }); + }; + if ($attrs.enableDrag || $attrs.enableDrop) { $scope.placeElm = null; // $scope.dragBorder = 30; @@ -804,6 +903,8 @@ function fnInitTreeDnD($timeout, $http, $compile, $parse, $window, $document, $t beginAnd: true }, tree, + // Array to store watch deregistration functions for cleanup + _watchDeregistrations = [], _watches = [ [ 'enableDrag', @@ -977,7 +1078,8 @@ function fnInitTreeDnD($timeout, $http, $compile, $parse, $window, $document, $t } if ($attrs.treeData) { - $scope.$watch( + // Store deregistration function for treeData watch + var unwatchTreeData = $scope.$watch( $attrs.treeData, function (val) { if (angular.equals(val, $scope.treeData)) { return; @@ -989,8 +1091,70 @@ function fnInitTreeDnD($timeout, $http, $compile, $parse, $window, $document, $t } }, true ); + _watchDeregistrations.push(unwatchTreeData); } + $scope.$on('$destroy', function () { + // Cancel any pending timeouts + if (timeReloadData) { + $timeout.cancel(timeReloadData); + timeReloadData = null; + } + tmpTreeData = null; + + // Deregister all watches to prevent memory leaks + var i, len; + for (i = 0, len = _watchDeregistrations.length; i < len; i++) { + if (_watchDeregistrations[i]) { + _watchDeregistrations[i](); + } + } + _watchDeregistrations.length = 0; + + // Clear all scope references in $globals + if ($scope.$globals) { + var keys = Object.keys($scope.$globals); + for (i = 0, len = keys.length; i < len; i++) { + delete $scope.$globals[keys[i]]; + } + $scope.$globals = null; + } + + // Remove and clean up placeholder element + if ($scope.placeElm) { + $scope.placeElm.remove(); + $scope.placeElm = null; + } + + // Remove and clean up status element + if ($scope.statusElm) { + $scope.statusElm.remove(); + $scope.statusElm = null; + } + + // Clear tree node references + if ($scope.tree_nodes) { + $scope.tree_nodes.length = 0; + $scope.tree_nodes = null; + } + + // Clear treeData references + if ($scope.treeData) { + $scope.treeData = null; + } + + // Clear callbacks to break circular references + $scope.$callbacks = null; + + // Clear column definitions + $scope.colDefinitions = null; + + // Clear tree control reference + if (tree) { + tree = null; + } + }); + function timeLoadData() { $scope.treeData = tmpTreeData; reload_data(); @@ -1048,7 +1212,8 @@ function fnInitTreeDnD($timeout, $http, $compile, $parse, $window, $document, $t return;//jmp } if (typeof $attrs[nameAttr] === 'string') { - $scope.$watch( + // Store deregistration function for cleanup + var unwatchFn = $scope.$watch( $attrs[nameAttr], function (val) { if (typeof type === 'string' && typeof val === type || angular.isArray(type) && type.indexOf(typeof val) > -1 @@ -1067,6 +1232,7 @@ function fnInitTreeDnD($timeout, $http, $compile, $parse, $window, $document, $t } }, true ); + _watchDeregistrations.push(unwatchFn); } else { if (angular.isFunction(fnNotExist)) { @@ -1147,11 +1313,13 @@ function fnInitTreeDnD($timeout, $http, $compile, $parse, $window, $document, $t node.__parent__ = parent; _len = node.__children__.length; - if (angular.isUndefinedOrNull(node.__expanded__) && _len > 0) { + var _hasChildren = _len > 0 || node.__has_children__ === true || node.__lazy__ === true; + + if (angular.isUndefinedOrNull(node.__expanded__) && _hasChildren) { node.__expanded__ = level < $scope.expandLevel; } - if (_len === 0) { + if (!_hasChildren) { _icon = -1; } else { if (node.__expanded__) { @@ -1205,9 +1373,18 @@ function fnInitTreeDnD($timeout, $http, $compile, $parse, $window, $document, $t function init_data(data) { - // clear memory - if (angular.isDefined($scope.tree_nodes)) { - delete $scope.tree_nodes; + // clear memory - properly clean up old nodes to prevent memory leaks + if (angular.isDefined($scope.tree_nodes) && $scope.tree_nodes) { + // Clear internal properties that may hold references + var i, len, node; + for (i = 0, len = $scope.tree_nodes.length; i < len; i++) { + node = $scope.tree_nodes[i]; + if (node) { + // Clear the __inited__ flag that creates circular references + delete node.__inited__; + } + } + $scope.tree_nodes.length = 0; } $scope.tree_nodes = data; @@ -1422,1916 +1599,2024 @@ function fnInitTreeDnD($timeout, $http, $compile, $parse, $window, $document, $t angular.module('ntt.TreeDnD') - .factory('$TreeDnDConvert', function () { - var _$initConvert = { - line2tree: function (data, primaryKey, parentKey, callback) { - callback = typeof callback === 'function' ? callback : function () { - }; - if (!data || data.length === 0 || !primaryKey || !parentKey) { - return []; + .factory('$TreeDnDDrag', [ + '$timeout', '$TreeDnDHelper', + function ($timeout, $TreeDnDHelper) { + function _fnPlaceHolder(e, $params) { + if ($params.placeElm) { + var _offset = $TreeDnDHelper.offset($params.placeElm); + if (_offset.top <= e.pageY && e.pageY <= _offset.top + _offset.height && + _offset.left <= e.pageX && e.pageX <= _offset.left + _offset.width + ) { + return true; + } } - var tree = [], - rootIds = [], - item = data[0], - _primary = item[primaryKey], - treeObjs = {}, - parentId, parent, - len = data.length, - i = 0; + return false; + } - while (i < len) { - item = data[i++]; - callback(item); - _primary = item[primaryKey]; - treeObjs[_primary] = item; - } - i = 0; - while (i < len) { - item = data[i++]; - callback(item); - _primary = item[primaryKey]; - treeObjs[_primary] = item; - parentId = item[parentKey]; - if (parentId) { - parent = treeObjs[parentId]; - if (parent) { - if (parent.__children__) { - parent.__children__.push(item); - } else { - parent.__children__ = [item]; - } - } - } else { - rootIds.push(_primary); - } + function _fnDragStart(e, $params) { + if (!$params.hasTouch && (e.button === 2 || e.which === 3)) { + // disable right click + return; } - len = rootIds.length; - for (i = 0; i < len; i++) { - tree.push(treeObjs[rootIds[i]]); + + if (e.uiTreeDragging || e.originalEvent && e.originalEvent.uiTreeDragging) { // event has already fired in other scope. + return; } - return tree; - }, - tree2tree: function access_child(data, containKey, callback) { - callback = typeof callback === 'function' ? callback : function () { - }; - var _tree = [], - _i, - _len = data ? data.length : 0, - _copy, _child; - for (_i = 0; _i < _len; _i++) { - _copy = angular.copy(data[_i]); - callback(_copy); - if (angular.isArray(_copy[containKey]) && _copy[containKey].length > 0) { - _child = access_child(_copy[containKey], containKey, callback); - delete _copy[containKey]; - _copy.__children__ = _child; - } - _tree.push(_copy); + + // the element which is clicked. + var eventElm = angular.element(e.target), + eventScope = eventElm.scope(); + if (!eventScope || !eventScope.$type) { + return; } - return _tree; - } - }; + // if (eventScope.$type !== 'TreeDnDNode') { // Check if it is a node or a handle + // return; + // } - return _$initConvert; - }); + if (eventScope.$type !== 'TreeDnDNodeHandle') { // If the node has a handle, then it should be clicked by the handle + return; + } -angular.module('ntt.TreeDnD') - .factory('$TreeDnDHelper', [ - '$document', '$window', - function ($document, $window) { - var _$helper = { - nodrag: function (targetElm) { - return typeof targetElm.attr('data-nodrag') !== 'undefined'; - }, - eventObj: function (e) { - var obj = e; - if (e.targetTouches !== undefined) { - obj = e.targetTouches.item(0); - } else if (e.originalEvent !== undefined && e.originalEvent.targetTouches !== undefined) { - obj = e.originalEvent.targetTouches.item(0); + var eventElmTagName = eventElm.prop('tagName').toLowerCase(), + dragScope, + _$scope = $params.$scope; + if (eventElmTagName === 'input' + || eventElmTagName === 'textarea' + || eventElmTagName === 'button' + || eventElmTagName === 'select') { // if it's a input or button, ignore it + return; + } + // check if it or it's parents has a 'data-nodrag' attribute + while (eventElm && eventElm[0] && eventElm[0] !== $params.element) { + if ($TreeDnDHelper.nodrag(eventElm)) { // if the node mark as `nodrag`, DONOT drag it. + return; } - return obj; - }, - dragInfo: function (scope) { - var _node = scope.getData(), - _tree = scope.getScopeTree(), - _parent = scope.getNode(_node.__parent_real__); + eventElm = eventElm.parent(); + } - return { - node: _node, - parent: _parent, - move: { - parent: _parent, - pos: _node.__index__ - }, - scope: scope, - target: _tree, - drag: _tree, - drop: scope.getPrevSibling(_node), - changed: false - }; - }, - height: function (element) { - return element.prop('scrollHeight'); - }, - width: function (element) { - return element.prop('scrollWidth'); - }, - offset: function (element) { - var boundingClientRect = element[0].getBoundingClientRect(); - return { - width: element.prop('offsetWidth'), - height: element.prop('offsetHeight'), - top: boundingClientRect.top + ($window.pageYOffset || $document[0].body.scrollTop || $document[0].documentElement.scrollTop), - left: boundingClientRect.left + ($window.pageXOffset || $document[0].body.scrollLeft || $document[0].documentElement.scrollLeft) - }; - }, - positionStarted: function (e, target) { - return { - offsetX: e.pageX - this.offset(target).left, - offsetY: e.pageY - this.offset(target).top, - startX: e.pageX, - lastX: e.pageX, - startY: e.pageY, - lastY: e.pageY, - nowX: 0, - nowY: 0, - distX: 0, - distY: 0, - dirAx: 0, - dirX: 0, - dirY: 0, - lastDirX: 0, - lastDirY: 0, - distAxX: 0, - distAxY: 0 - }; - }, - positionMoved: function (e, pos, firstMoving) { - // mouse position last events - pos.lastX = pos.nowX; - pos.lastY = pos.nowY; + e.uiTreeDragging = true; // stop event bubbling + if (e.originalEvent) { + e.originalEvent.uiTreeDragging = true; + } + e.preventDefault(); - // mouse position this events - pos.nowX = e.pageX; - pos.nowY = e.pageY; + dragScope = eventScope.getScopeNode(); - // distance mouse moved between events - pos.distX = pos.nowX - pos.lastX; - pos.distY = pos.nowY - pos.lastY; - - // direction mouse was moving - pos.lastDirX = pos.dirX; - pos.lastDirY = pos.dirY; - - // direction mouse is now moving (on both axis) - pos.dirX = pos.distX === 0 ? 0 : pos.distX > 0 ? 1 : -1; - pos.dirY = pos.distY === 0 ? 0 : pos.distY > 0 ? 1 : -1; - - // axis mouse is now moving on - var newAx = Math.abs(pos.distX) > Math.abs(pos.distY) ? 1 : 0; - - // do nothing on first move - if (firstMoving) { - pos.dirAx = newAx; - pos.moving = true; - return; - } + $params.dragInfo = $TreeDnDHelper.dragInfo(dragScope); - // calc distance moved on this axis (and direction) - if (pos.dirAx !== newAx) { - pos.distAxX = 0; - pos.distAxY = 0; - } else { - pos.distAxX += Math.abs(pos.distX); - if (pos.dirX !== 0 && pos.dirX !== pos.lastDirX) { - pos.distAxX = 0; - } - pos.distAxY += Math.abs(pos.distY); - if (pos.dirY !== 0 && pos.dirY !== pos.lastDirY) { - pos.distAxY = 0; - } - } - pos.dirAx = newAx; - }, - replaceIndent: function (scope, element, indent, attr) { - attr = attr || 'left'; - angular.element(element.children()[0]).css(attr, scope.$callbacks.calsIndent(indent)); + if (!_$scope.$callbacks.beforeDrag(dragScope, $params.dragInfo)) { + return; } - }; - return _$helper; - }] - ); + $params.firstMoving = true; + _$scope.setDragging($params.dragInfo); -angular.module('ntt.TreeDnD') - .factory('$TreeDnDPlugin', [ - '$injector', - function ($injector) { - var _fnget = function (name) { - if (angular.isDefined($injector) && $injector.has(name)) { - return $injector.get(name); + var eventObj = $TreeDnDHelper.eventObj(e); + $params.pos = $TreeDnDHelper.positionStarted(eventObj, dragScope.$element); + + if (dragScope.isTable) { + $params.dragElm = angular.element($params.$window.document.createElement('table')) + .addClass(_$scope.$class.tree) + .addClass(_$scope.$class.drag) + .addClass(_$scope.$tree_class); + } else { + $params.dragElm = angular.element($params.$window.document.createElement('ul')) + .addClass(_$scope.$class.drag) + .addClass('tree-dnd-nodes') + .addClass(_$scope.$tree_class); } - return null; - }; - return _fnget; - }] - ); -angular.module('ntt.TreeDnD') - .factory('$TreeDnDTemplate', [ - '$templateCache', - function ($templateCache) { - var templatePath = 'template/TreeDnD/TreeDnD.html', - copyPath = 'template/TreeDnD/TreeDnDStatusCopy.html', - movePath = 'template/TreeDnD/TreeDnDStatusMove.html', - scopes = {}, - temp, - _$init = { - setMove: function (path, scope) { - if (!scopes[scope.$id]) { - scopes[scope.$id] = {}; - } - scopes[scope.$id].movePath = path; - }, - setCopy: function (path, scope) { - if (!scopes[scope.$id]) { - scopes[scope.$id] = {}; - } - scopes[scope.$id].copyPath = path; - }, - getPath: function () { - return templatePath; - }, - getCopy: function (scope) { - if (scopes[scope.$id] && scopes[scope.$id].copyPath) { - temp = $templateCache.get(scopes[scope.$id].copyPath); - if (temp) { - return temp; - } - } - return $templateCache.get(copyPath); - }, - getMove: function (scope) { - if (scopes[scope.$id] && scopes[scope.$id].movePath) { - temp = $templateCache.get(scopes[scope.$id].movePath); - if (temp) { - return temp; - } - } - return $templateCache.get(movePath); + $params.dragElm.css( + { + 'width': $TreeDnDHelper.width(dragScope.$element) + 'px', + 'z-index': 9995 } - }; + ); - return _$init; - }] - ); + $params.offsetEdge = 0; + var _width = $TreeDnDHelper.width(dragScope.$element), + _scope = dragScope, + _element = _scope.$element, + _clone, + _needCollapse = !!_$scope.enabledCollapse, + _copied = false, + _tbody, + _frag; -angular.module('ntt.TreeDnD') - .factory('$TreeDnDViewport', fnInitTreeDnDViewport); + if (_scope.isTable) { + $params.offsetEdge = $params.dragInfo.node.__level__ - 1; + _tbody = angular.element(document.createElement('tbody')); + _frag = angular.element(document.createDocumentFragment()); -fnInitTreeDnDViewport.$inject = ['$window', '$document', '$timeout', '$q', '$compile']; + _$scope.for_all_descendants( + $params.dragInfo.node, function (_node, _parent) { + _scope = _$scope.getScope(_node); + _element = _scope && _scope.$element; + if (_scope && _element) { + if (!_copied) { + _clone = _element.clone(); -function fnInitTreeDnDViewport($window, $document, $timeout, $q, $compile) { + $TreeDnDHelper.replaceIndent( + _$scope, + _clone, + _node.__level__ - $params.offsetEdge, + 'padding-left' + ); - var viewport = null, - isUpdating = false, - isRender = false, - updateAgain = false, - viewportRect, - items = [], - nodeTemplate, - updateTimeout, - renderTime, - $initViewport = { - setViewport: setViewport, - getViewport: getViewport, - add: add, - setTemplate: setTemplate, - getItems: getItems, - updateDelayed: updateDelayed - }, - eWindow = angular.element($window); + _frag.append(_clone); - eWindow.on('load resize scroll', updateDelayed); + // skip all, just clone parent + if (_needCollapse) { + _copied = true; + } - return $initViewport; + // hide if have status Move; + if (_$scope.enabledMove && _$scope.$class.hidden && + (!_parent || _node.__visible__ || _parent.__visible__ && _parent.__expanded__)) { + _element.addClass(_$scope.$class.hidden); + } + } + } + // skip children of node not expand. + return _copied || _node.__visible__ === false || _node.__expanded__ === false; - function update() { + }, null, !_needCollapse + ); + _tbody.append(_frag); + $params.dragElm.append(_tbody); + } else { - viewportRect = { - width: eWindow.prop('offsetWidth') || document.documentElement.clientWidth, - height: eWindow.prop('offsetHeight') || document.documentElement.clientHeight, - top: $document[0].body.scrollTop || $document[0].documentElement.scrollTop, - left: $document[0].body.scrollLeft || $document[0].documentElement.scrollLeft - }; + _clone = _element.clone(); + if (_needCollapse) { + _clone[0].querySelector('[tree-dnd-nodes]').remove(); + } - if (isUpdating || isRender) { - updateAgain = true; - return; - } - isUpdating = true; + // hide if have status Move; + $params.dragElm.append(_clone); + if (_$scope.enabledMove && _$scope.$class.hidden) { + _element.addClass(_$scope.$class.hidden); + } + } - recursivePromise(); - } + $params.dragElm.css( + { + 'left': eventObj.pageX - $params.pos.offsetX + _$scope.$callbacks.calsIndent( + $params.offsetEdge + 1, + true, + true + ) + 'px', + 'top': eventObj.pageY - $params.pos.offsetY + 'px' + } + ); + // moving item with descendant + $params.$document.find('body').append($params.dragElm); + if (_$scope.$callbacks.droppable()) { + $params.placeElm = _$scope.initPlace(dragScope.$element, $params.dragElm); - function recursivePromise() { - if (isRender) { - return; - } + if (dragScope.isTable) { + $TreeDnDHelper.replaceIndent(_$scope, $params.placeElm, $params.dragInfo.node.__level__); + } - var number = number > 0 ? number : items.length, item; + $params.placeElm.css('width', _width); + } - if (number > 0) { - item = items[0]; + _$scope.showPlace(); + _$scope.targeting = true; - isRender = true; - renderTime = $timeout(function () { - //item.element.html(nodeTemplate); - //$compile(item.element.contents())(item.scope); + if (_$scope.enabledStatus) { + _$scope.refreshStatus(); + _$scope.setPositionStatus(e); + } - items.splice(0, 1); - isRender = false; - number--; - $timeout.cancel(renderTime); - recursivePromise(); - }, 0); + angular.element($params.$document).bind('touchend', $params.dragEndEvent); + angular.element($params.$document).bind('touchcancel', $params.dragEndEvent); + angular.element($params.$document).bind('touchmove', $params.dragMoveEvent); + angular.element($params.$document).bind('mouseup', $params.dragEndEvent); + angular.element($params.$document).bind('mousemove', $params.dragMoveEvent); + angular.element($params.$document).bind('mouseleave', $params.dragCancelEvent); - } else { - isUpdating = false; - if (updateAgain) { - updateAgain = false; - update(); - } - } + $params.document_height = Math.max( + $params.body.scrollHeight, + $params.body.offsetHeight, + $params.html.clientHeight, + $params.html.scrollHeight, + $params.html.offsetHeight + ); - } + $params.document_width = Math.max( + $params.body.scrollWidth, + $params.body.offsetWidth, + $params.html.clientWidth, + $params.html.scrollWidth, + $params.html.offsetWidth + ); + } - /** - * Check if a point is inside specified bounds - * @param x - * @param y - * @param bounds - * @returns {boolean} - */ - function pointIsInsideBounds(x, y, bounds) { - return x >= bounds.left && - y >= bounds.top && - x <= bounds.left + bounds.width && - y <= bounds.top + bounds.height; - } + function _fnDragMove(e, $params) { + var _$scope = $params.$scope; + if (!$params.dragStarted) { + if (!$params.dragDelaying) { + $params.dragStarted = true; + _$scope.$safeApply( + function () { + _$scope.$callbacks.dragStart($params.dragInfo); + } + ); + } + return; + } - /** - * @name setViewport - * @desciption Set the viewport element - * @param element - */ - function setViewport(element) { - viewport = element; - } + if ($params.dragElm) { + e.preventDefault(); + if ($params.$window.getSelection) { + $params.$window.getSelection().removeAllRanges(); + } else if ($params.$window.document.selection) { + $params.$window.document.selection.empty(); + } - /** - * Return the current viewport - * @returns {*} - */ - function getViewport() { - return viewport; - } + var eventObj = $TreeDnDHelper.eventObj(e), + leftElmPos = eventObj.pageX - $params.pos.offsetX, + topElmPos = eventObj.pageY - $params.pos.offsetY; - /** - * trigger an update - */ - function updateDelayed() { - $timeout.cancel(updateTimeout); - updateTimeout = $timeout(function () { - update(); - }, 0); - } + //dragElm can't leave the screen on the left + if (leftElmPos < 0) { + leftElmPos = 0; + } - /** - * Add listener for event - * @param element - * @param callback - */ - function add(scope, element) { - updateDelayed(); - items.push({ - element: element, - scope: scope - }); - } + //dragElm can't leave the screen on the top + if (topElmPos < 0) { + topElmPos = 0; + } - function setTemplate(scope, template) { - nodeTemplate = template; - } + //dragElm can't leave the screen on the bottom + if (topElmPos + 10 > $params.document_height) { + topElmPos = $params.document_height - 10; + } - /** - * Get list of items - * @returns {Array} - */ - function getItems() { - return items; - } -} + //dragElm can't leave the screen on the right + if (leftElmPos + 10 > $params.document_width) { + leftElmPos = $params.document_width - 10; + } -angular.module('ntt.TreeDnD') - .factory('$TreeDnDFilter', [ - '$filter', function ($filter) { - return fnInitFilter; + $params.dragElm.css( + { + 'left': leftElmPos + _$scope.$callbacks.calsIndent( + $params.offsetEdge + 1, + true, + true + ) + 'px', + 'top': topElmPos + 'px' + } + ); - function for_all_descendants(options, node, fieldChild, fnBefore, fnAfter, parentPassed) { - if (!angular.isFunction(fnBefore)) { - return null; - } + if (_$scope.enabledStatus) { + _$scope.setPositionStatus(e); + } - var _i, _len, _nodes, - _nodePassed = fnBefore(options, node), - _childPassed = false, - _filter_index = options.filter_index; + var top_scroll = window.pageYOffset || $params.$window.document.documentElement.scrollTop, + bottom_scroll = top_scroll + (window.innerHeight || $params.$window.document.clientHeight || $params.$window.document.clientHeight); + // to scroll down if cursor y-position is greater than the bottom position the vertical scroll + if (bottom_scroll < eventObj.pageY && bottom_scroll <= $params.document_height) { + window.scrollBy(0, 10); + } + // to scroll top if cursor y-position is less than the top position the vertical scroll + if (top_scroll > eventObj.pageY) { + window.scrollBy(0, -10); + } - if (angular.isDefined(node[fieldChild])) { - _nodes = node[fieldChild]; - _len = _nodes.length; + $TreeDnDHelper.positionMoved(e, $params.pos, $params.firstMoving); - options.filter_index = 0; - for (_i = 0; _i < _len; _i++) { - _childPassed = for_all_descendants( - options, - _nodes[_i], - fieldChild, - fnBefore, - fnAfter, - _nodePassed || parentPassed - ) || _childPassed; + if ($params.firstMoving) { + $params.firstMoving = false; + return; } + // check if add it as a child node first - // restore filter_index of node - options.filter_index = _filter_index; - } + var targetX = eventObj.pageX - $params.$window.document.body.scrollLeft, + targetY = eventObj.pageY - (window.pageYOffset || $params.$window.document.documentElement.scrollTop), - if (angular.isFunction(fnAfter)) { - fnAfter(options, node, _nodePassed === true, _childPassed === true, parentPassed === true); - } + targetElm, + targetScope, + targetBefore, + targetOffset, + isChanged = true, + isVeritcal = true, + isEmpty, + isSwapped, + _scope, + _target, + _parent, + _info = $params.dragInfo, + _move = _info.move, + _drag = _info.node, + _drop = _info.drop, + treeScope = _info.target, + fnSwapTree, + isHolder = _fnPlaceHolder(e, $params); - return _nodePassed || _childPassed; - } + if (!isHolder) { + /* when using elementFromPoint() inside an iframe, you have to call + elementFromPoint() twice to make sure IE8 returns the correct value + $params.$window.document.elementFromPoint(targetX, targetY);*/ - /** - * Check data with callback - * @param {string|object|function|regex} callback - * @param {*} data - * @returns {null|boolean} - * @private - */ - function _fnCheck(callback, data) { - if (angular.isUndefinedOrNull(data) || angular.isArray(data)) { - return null; - } + targetElm = angular.element( + $params.$window.document.elementFromPoint( + targetX, + targetY + ) + ); - if (angular.isFunction(callback)) { - return callback(data, $filter); - } else { - if (typeof callback === 'boolean') { - data = !!data; - return data === callback; - } else if (angular.isDefined(callback)) { - try { - var _regex = new RegExp(callback); - return _regex.test(data); - } - catch (err) { - if (typeof data === 'string') { - return data.indexOf(callback) > -1; - } else { - return null; - } + targetScope = targetElm.scope(); + if (!targetScope || !targetScope.$callbacks || !targetScope.$callbacks.droppable()) { + // Not allowed Drop Item + return; } - } else { - return null; - } - } - } - /** - * `fnProcess` to call `_fnCheck`. If `condition` is `array` then call `for_each_filter` - * else will call `_fnCheck`. Specical `condition.field` is `_$` then apply `condition.callback` for all field, if have `field` invaild then `return true`. - * - * @param node - * @param condition - * @param isAnd - * @returns {null|boolean} - * @private - */ - function _fnProccess(node, condition, isAnd) { - if (angular.isArray(condition)) { - return for_each_filter(node, condition, isAnd); - } else { - var _key = condition.field, - _callback = condition.callback, - _iO, _keysO, _lenO; + fnSwapTree = function () { + treeScope = targetScope.getScopeTree(); + _target = _info.target; - if (_key === '_$') { - _keysO = Object.keys(node); - _lenO = _keysO.length; - for (_iO = 0; _iO < _lenO; _iO++) { - if (_fnCheck(_callback, node[_keysO[_iO]])) { - return true; - } - } - } else if (angular.isDefined(node[_key])) { - return _fnCheck(_callback, node[_key]); - } - } - return null; - } + if (_info.target !== treeScope) { + // Replace by place-holder new + _target.hidePlace(); + _target.targeting = false; + treeScope.targeting = true; - /** - * - * @param {object} node - * @param {array} conditions Array `conditions` - * @param {boolean} isAnd check with condition `And`, if `And` then `return false` when all `false` - * @returns {null|boolean} - */ - function for_each_filter(node, conditions, isAnd) { - var i, len = conditions.length || 0, passed = false; - if (len === 0) { - return null; - } + _info.target = treeScope; + $params.placeElm = treeScope.initPlace(targetScope.$element, $params.dragElm); - for (i = 0; i < len; i++) { - if (_fnProccess(node, conditions[i], !isAnd)) { - passed = true; - // if condition `or` then return; - if (!isAnd) { + _target = null; + isSwapped = true; + } return true; - } - } else { + }; - // if condition `and` and result in fnProccess = false then return; - if (isAnd) { - return false; + if (angular.isFunction(targetScope.getScopeNode)) { + targetScope = targetScope.getScopeNode(); + if (!fnSwapTree()) { + return; + } + } else { + if (targetScope.$type === 'TreeDnDNodes' || targetScope.$type === 'TreeDnD') { + if (targetScope.tree_nodes) { + if (targetScope.tree_nodes.length === 0) { + if (!fnSwapTree()) { + return; + } + // Empty + isEmpty = true; + } + } else { + return; + } + } else { + return; + } } } - } - return passed; - } + if ($params.pos.dirAx && !isSwapped || isHolder) { + isVeritcal = false; + targetScope = _info.scope; + } - /** - * Will call _fnAfter to clear data no need - * @param {object} options - * @param {object} node - * @param {boolean} isNodePassed - * @param {boolean} isChildPassed - * @param {boolean} isParentPassed - * @private - */ - function _fnAfter(options, node, isNodePassed, isChildPassed, isParentPassed) { - if (isNodePassed === true) { - node.__filtered__ = true; - node.__filtered_visible__ = true; - node.__filtered_index__ = options.filter_index++; - return; //jmp - } else if (isChildPassed === true && options.showParent === true - || isParentPassed === true && options.showChild === true) { - node.__filtered__ = false; - node.__filtered_visible__ = true; - node.__filtered_index__ = options.filter_index++; - return; //jmp - } + if (!targetScope.$element && !targetScope) { + return; + } - // remove attr __filtered__ - delete node.__filtered__; - delete node.__filtered_visible__; - delete node.__filtered_index__; - } + if (isEmpty) { + _move.parent = null; + _move.pos = 0; - /** - * `fnBefore` will called when `for_all_descendants` of `node` checking. - * If `filter` empty then return `true` else result of function `_fnProccess` {@see _fnProccess} - * - * @param {object} options - * @param {object} node - * @returns {null|boolean} - * @private - */ - function _fnBefore(options, node) { - if (options.filter.length === 0) { - return true; - } else { - return _fnProccess(node, options.filter, options.beginAnd || false); - } - } + _drop = null; + } else { + // move vertical + if (isVeritcal) { + targetElm = targetScope.$element; // Get the element of tree-dnd-node + if (angular.isUndefinedOrNull(targetElm)) { + return; + } + targetOffset = $TreeDnDHelper.offset(targetElm); - /** - * `fnBeforeClear` will called when `for_all_descendants` of `node` checking. - * Alway false to Clear Filter empty - * - * @param {object} options - * @param {object} node - * @returns {null|boolean} - * @private - */ - function _fnBeforeClear(options, node) { - return false; - } + if (targetScope.horizontal && !targetScope.isTable) { + targetBefore = eventObj.pageX < targetOffset.left + $TreeDnDHelper.width(targetElm) / 2; + } else { + if (targetScope.isTable) { + targetBefore = eventObj.pageY < targetOffset.top + $TreeDnDHelper.height(targetElm) / 2; + } else { + var _height = $TreeDnDHelper.height(targetElm); - /** - * `_fnConvert` to convert `filter` `object` to `array` invaild. - * - * @param {object|array} filters - * @returns {array} Instead of `filter` or new array invaild *(converted from filter)* - * @private - */ - function _fnConvert(filters) { - var _iF, _lenF, _keysF, - _filter, - _state; - // convert filter object to array filter - if (angular.isObject(filters) && !angular.isArray(filters)) { - _keysF = Object.keys(filters); - _lenF = _keysF.length; - _filter = []; + if (targetScope.getElementChilds()) { + _height -= -$TreeDnDHelper.height(targetScope.getElementChilds()); + } - if (_lenF > 0) { - for (_iF = 0; _iF < _lenF; _iF++) { + if (eventObj.pageY > targetOffset.top + _height) { + return; + } - if (typeof filters[_keysF[_iF]] === 'string' && filters[_keysF[_iF]].length === 0) { - continue; - } else if (angular.isArray(filters[_keysF[_iF]])) { - _state = filters[_keysF[_iF]]; - } else if (angular.isObject(filters[_keysF[_iF]])) { - _state = _fnConvert(filters[_keysF[_iF]]); - } else { - _state = { - field: _keysF[_iF], - callback: filters[_keysF[_iF]] - }; + targetBefore = eventObj.pageY < targetOffset.top + _height / 2; + } } - _filter.push(_state); - } - } - _state = null; - return _filter; - } - else { - return filters; - } - } - /** - * `fnInitFilter` function is constructor of service `$TreeDnDFilter`. - * @constructor - * @param {object|array} treeData - * @param {object|array} filters - * @param {object} options - * @param {string} keyChild - * @returns {array} Return `treeData` or `treeData` with `filter` - * @private - */ - function fnInitFilter(treeData, filters, options, keyChild) { - if (!angular.isArray(treeData) - || treeData.length === 0) { - return treeData; - } + if (!angular.isFunction(targetScope.getData)) { + return; + } - var _i, _len, - _filter; + _target = targetScope.getData(); + _parent = targetScope.getNode(_target.__parent_real__); - _filter = _fnConvert(filters); - if (!(angular.isArray(_filter) || angular.isObject(_filter)) - || _filter.length === 0) { - for (_i = 0, _len = treeData.length; _i < _len; _i++) { - for_all_descendants( - options, - treeData[_i], - keyChild || '__children__', - _fnBeforeClear, _fnAfter - ); - } - return treeData; - } + if (targetBefore) { + var _prev = targetScope.getPrevSibling(_target); - options.filter = _filter; - options.filter_index = 0; - for (_i = 0, _len = treeData.length; _i < _len; _i++) { - for_all_descendants( - options, - treeData[_i], - keyChild || '__children__', - _fnBefore, _fnAfter - ); - } + _move.parent = _parent; + _move.pos = angular.isDefined(_prev) ? _prev.__index__ + 1 : 0; - return treeData; - } + _drop = _prev; + } else { + if (_target.__expanded__ && !(_target.__children__.length === 1 && _target.__index_real__ === _drag.__parent_real__)) { + _move.parent = _target; + _move.pos = 0; - }] - ); + _drop = null; + } else { + _move.parent = _parent; + _move.pos = _target.__index__ + 1; -angular.module('ntt.TreeDnD') - .factory('$TreeDnDOrderBy', [ - '$filter', - function ($filter) { - var _fnOrderBy = $filter('orderBy'), - for_all_descendants = function for_all_descendants(options, node, name, fnOrderBy) { - var _i, _len, _nodes; + _drop = _target; + } + } + } else { + // move horizontal + if ($params.pos.dirAx && $params.pos.distAxX >= treeScope.dragBorder) { + $params.pos.distAxX = 0; + // increase horizontal level if previous sibling exists and is not collapsed + if ($params.pos.distX > 0) { + _parent = _drop; + if (!_parent) { + if (_move.pos - 1 >= 0) { + _parent = _move.parent.__children__[_move.pos - 1]; + } else { + return; + } + } - if (angular.isDefined(node[name])) { - _nodes = node[name]; - _len = _nodes.length; - // OrderBy children - for (_i = 0; _i < _len; _i++) { - _nodes[_i] = for_all_descendants(options, _nodes[_i], name, fnOrderBy); - } + if (_info.drag === _info.target && _parent === _drag && _$scope.enabledMove) { + _parent = treeScope.getPrevSibling(_parent); + } - node[name] = fnOrderBy(node[name], options); - } - return node; - }, - _fnOrder = function _fnOrder(list, orderBy) { - return _fnOrderBy(list, orderBy); - }, - _fnMain = function _fnMain(treeData, orderBy) { - if (!angular.isArray(treeData) - || treeData.length === 0 - || !(angular.isArray(orderBy) || angular.isObject(orderBy) || angular.isString(orderBy) || angular.isFunction(orderBy)) - || orderBy.length === 0 && !angular.isFunction(orderBy)) { - return treeData; - } + if (_parent && _parent.__visible__) { + var _len = _parent.__children__.length; - var _i, _len; + _move.parent = _parent; + _move.pos = _len; - for (_i = 0, _len = treeData.length; _i < _len; _i++) { - treeData[_i] = for_all_descendants( - orderBy, - treeData[_i], - '__children__', - _fnOrder - ); - } + if (_len > 0) { + _drop = _parent.__children__[_len - 1]; + } else { + _drop = null; + } + } else { + // Not changed + return; + } + } else if ($params.pos.distX < 0) { + _target = _move.parent; + if (_target && + (_target.__children__.length === 0 || + _target.__children__.length - 1 < _move.pos || + _info.drag === _info.target && + _target.__index_real__ === _drag.__parent_real__ && + _target.__children__.length - 1 === _drag.__index__ && _$scope.enabledMove) + ) { + _parent = treeScope.getNode(_target.__parent_real__); - return _fnOrder(treeData, orderBy); - }; + _move.parent = _parent; + _move.pos = _target.__index__ + 1; - return _fnMain; - }] - ); + _drop = _target; + } else { + // Not changed + return; + } + } else { + return; + } + } else { + // limited + return; + } + } + } -angular.module('ntt.TreeDnD') - .factory('$TreeDnDDrag', [ - '$timeout', '$TreeDnDHelper', - function ($timeout, $TreeDnDHelper) { - function _fnPlaceHolder(e, $params) { - if ($params.placeElm) { - var _offset = $TreeDnDHelper.offset($params.placeElm); - if (_offset.top <= e.pageY && e.pageY <= _offset.top + _offset.height && - _offset.left <= e.pageX && e.pageX <= _offset.left + _offset.width + if (_info.drag === _info.target && + _move.parent && + _drag.__parent_real__ === _move.parent.__index_real__ && + _drag.__index__ === _move.pos ) { - return true; + isChanged = false; } - } - return false; - } - function _fnDragStart(e, $params) { - if (!$params.hasTouch && (e.button === 2 || e.which === 3)) { - // disable right click - return; - } + if (treeScope.$callbacks.accept(_info, _move, isChanged)) { + _info.move = _move; + _info.drop = _drop; + _info.changed = isChanged; + _info.scope = targetScope; - if (e.uiTreeDragging || e.originalEvent && e.originalEvent.uiTreeDragging) { // event has already fired in other scope. - return; - } + if (targetScope.isTable) { + $TreeDnDHelper.replaceIndent( + treeScope, + $params.placeElm, + angular.isUndefinedOrNull(_move.parent) ? 1 : _move.parent.__level__ + 1 + ); - // the element which is clicked. - var eventElm = angular.element(e.target), - eventScope = eventElm.scope(); - if (!eventScope || !eventScope.$type) { - return; - } - // if (eventScope.$type !== 'TreeDnDNode') { // Check if it is a node or a handle - // return; - // } + if (_drop) { + _parent = (_move.parent ? _move.parent.__children__ : null) || _info.target.treeData; - if (eventScope.$type !== 'TreeDnDNodeHandle') { // If the node has a handle, then it should be clicked by the handle - return; - } + if (_drop.__index__ < _parent.length - 1) { + // Find fast + _drop = _parent[_drop.__index__ + 1]; + _scope = _info.target.getScope(_drop); + _scope.$element[0].parentNode.insertBefore( + $params.placeElm[0], + _scope.$element[0] + ); + } else { + _target = _info.target.getLastDescendant(_drop); + _scope = _info.target.getScope(_target); + _scope.$element.after($params.placeElm); + } + } else { + _scope = _info.target.getScope(_move.parent); + if (_scope) { + if (_move.parent) { + _scope.$element.after($params.placeElm); - var eventElmTagName = eventElm.prop('tagName').toLowerCase(), - dragScope, - _$scope = $params.$scope; - if (eventElmTagName === 'input' - || eventElmTagName === 'textarea' - || eventElmTagName === 'button' - || eventElmTagName === 'select') { // if it's a input or button, ignore it - return; - } - // check if it or it's parents has a 'data-nodrag' attribute - while (eventElm && eventElm[0] && eventElm[0] !== $params.element) { - if ($TreeDnDHelper.nodrag(eventElm)) { // if the node mark as `nodrag`, DONOT drag it. - return; + } else { + _scope.getElementChilds().prepend($params.placeElm); + } + } + } + } else { + _scope = _info.target.getScope(_drop || _move.parent); + if (_drop) { + _scope.$element.after($params.placeElm); + } else { + _scope.getElementChilds().prepend($params.placeElm); + } + } + + treeScope.showPlace(); + + _$scope.$safeApply( + function () { + _$scope.$callbacks.dragMove(_info); + } + ); } - eventElm = eventElm.parent(); - } - e.uiTreeDragging = true; // stop event bubbling - if (e.originalEvent) { - e.originalEvent.uiTreeDragging = true; } - e.preventDefault(); + } - dragScope = eventScope.getScopeNode(); + function _fnDragEnd(e, $params) { + e.preventDefault(); - $params.dragInfo = $TreeDnDHelper.dragInfo(dragScope); + // Always unbind document listeners first to prevent leaks + // even if dragElm is null (edge case handling) + _fnUnbindDocumentListeners($params); - if (!_$scope.$callbacks.beforeDrag(dragScope, $params.dragInfo)) { - return; - } + if ($params.dragElm) { + var _passed = false, + _$scope = $params.$scope, + _scope = _$scope.getScope($params.dragInfo.node), + _element = _scope.$element; - $params.firstMoving = true; - _$scope.setDragging($params.dragInfo); + _$scope.$safeApply( + function () { + _passed = _$scope.$callbacks.beforeDrop($params.dragInfo); + } + ); - var eventObj = $TreeDnDHelper.eventObj(e); - $params.pos = $TreeDnDHelper.positionStarted(eventObj, dragScope.$element); - - if (dragScope.isTable) { - $params.dragElm = angular.element($params.$window.document.createElement('table')) - .addClass(_$scope.$class.tree) - .addClass(_$scope.$class.drag) - .addClass(_$scope.$tree_class); - } else { - $params.dragElm = angular.element($params.$window.document.createElement('ul')) - .addClass(_$scope.$class.drag) - .addClass('tree-dnd-nodes') - .addClass(_$scope.$tree_class); - } - - $params.dragElm.css( - { - 'width': $TreeDnDHelper.width(dragScope.$element) + 'px', - 'z-index': 9995 + // rollback all + if (_scope.isTable) { + _$scope.for_all_descendants( + $params.dragInfo.node, function (_node, _parent) { + _scope = _$scope.getScope(_node); + _element = _scope && _scope.$element; + if (_scope && _element && (!_parent && _node.__visible__ || _parent.__expanded__)) { + if (_$scope.$class.hidden) { + _element.removeClass(_$scope.$class.hidden); + } + } + return _node.__visible__ === false || _node.__expanded__ === false + }, null, true + ); + } else { + if (_$scope.$class.hidden) { + _element.removeClass(_$scope.$class.hidden); + } } - ); - - $params.offsetEdge = 0; - var _width = $TreeDnDHelper.width(dragScope.$element), - _scope = dragScope, - _element = _scope.$element, - _clone, - _needCollapse = !!_$scope.enabledCollapse, - _copied = false, - _tbody, - _frag; - - if (_scope.isTable) { - $params.offsetEdge = $params.dragInfo.node.__level__ - 1; - _tbody = angular.element(document.createElement('tbody')); - _frag = angular.element(document.createDocumentFragment()); - - _$scope.for_all_descendants( - $params.dragInfo.node, function (_node, _parent) { - _scope = _$scope.getScope(_node); - _element = _scope && _scope.$element; - if (_scope && _element) { - if (!_copied) { - _clone = _element.clone(); - $TreeDnDHelper.replaceIndent( - _$scope, - _clone, - _node.__level__ - $params.offsetEdge, - 'padding-left' - ); + $params.dragElm.remove(); + $params.dragElm = null; - _frag.append(_clone); + if (_$scope.enabledStatus) { + _$scope.hideStatus(); + } - // skip all, just clone parent - if (_needCollapse) { - _copied = true; - } + if (_$scope.$$apply) { + _$scope.$safeApply( + function () { + var _status = _$scope.$callbacks.dropped( + $params.dragInfo, + _passed + ); - // hide if have status Move; - if (_$scope.enabledMove && _$scope.$class.hidden && - (!_parent || _node.__visible__ || _parent.__visible__ && _parent.__expanded__)) { - _element.addClass(_$scope.$class.hidden); - } - } + _$scope.$callbacks.dragStop($params.dragInfo, _status); + clearData(); } - // skip children of node not expand. - return _copied || _node.__visible__ === false || _node.__expanded__ === false; - - }, null, !_needCollapse - ); - _tbody.append(_frag); - $params.dragElm.append(_tbody); - } else { - - _clone = _element.clone(); - if (_needCollapse) { - _clone[0].querySelector('[tree-dnd-nodes]').remove(); + ); + } else { + _fnBindDrag($params); + _$scope.$safeApply( + function () { + _$scope.$callbacks.dragStop($params.dragInfo, false); + clearData(); + } + ); } - // hide if have status Move; - $params.dragElm.append(_clone); - if (_$scope.enabledMove && _$scope.$class.hidden) { - _element.addClass(_$scope.$class.hidden); - } } - $params.dragElm.css( - { - 'left': eventObj.pageX - $params.pos.offsetX + _$scope.$callbacks.calsIndent( - $params.offsetEdge + 1, - true, - true - ) + 'px', - 'top': eventObj.pageY - $params.pos.offsetY + 'px' + function clearData() { + if ($params.dragInfo && $params.dragInfo.target) { + $params.dragInfo.target.hidePlace(); + $params.dragInfo.target.targeting = false; } - ); - // moving item with descendant - $params.$document.find('body').append($params.dragElm); - if (_$scope.$callbacks.droppable()) { - $params.placeElm = _$scope.initPlace(dragScope.$element, $params.dragElm); - if (dragScope.isTable) { - $TreeDnDHelper.replaceIndent(_$scope, $params.placeElm, $params.dragInfo.node.__level__); + $params.dragInfo = null; + if ($params.$scope) { + $params.$scope.$$apply = false; + $params.$scope.setDragging(null); } - - $params.placeElm.css('width', _width); } + } - _$scope.showPlace(); - _$scope.targeting = true; + /** + * Unbind all document-level event listeners + * Separated into its own function to ensure proper cleanup + */ + function _fnUnbindDocumentListeners($params) { + angular.element($params.$document).unbind('touchend', $params.dragEndEvent); + angular.element($params.$document).unbind('touchcancel', $params.dragEndEvent); + angular.element($params.$document).unbind('touchmove', $params.dragMoveEvent); + angular.element($params.$document).unbind('mouseup', $params.dragEndEvent); + angular.element($params.$document).unbind('mousemove', $params.dragMoveEvent); + angular.element($params.$window.document.body).unbind('mouseleave', $params.dragCancelEvent); + } - if (_$scope.enabledStatus) { - _$scope.refreshStatus(); - _$scope.setPositionStatus(e); + function _fnDragStartEvent(e, $params) { + if ($params.$scope.$callbacks.draggable()) { + _fnDragStart(e, $params); } + } - angular.element($params.$document).bind('touchend', $params.dragEndEvent); - angular.element($params.$document).bind('touchcancel', $params.dragEndEvent); - angular.element($params.$document).bind('touchmove', $params.dragMoveEvent); - angular.element($params.$document).bind('mouseup', $params.dragEndEvent); - angular.element($params.$document).bind('mousemove', $params.dragMoveEvent); - angular.element($params.$document).bind('mouseleave', $params.dragCancelEvent); - - $params.document_height = Math.max( - $params.body.scrollHeight, - $params.body.offsetHeight, - $params.html.clientHeight, - $params.html.scrollHeight, - $params.html.offsetHeight + function _fnBindDrag($params) { + $params.element.bind( + 'touchstart mousedown', function (e) { + $params.dragDelaying = true; + $params.dragStarted = false; + _fnDragStartEvent(e, $params); + $params.dragTimer = $timeout( + function () { + $params.dragDelaying = false; + }, $params.$scope.dragDelay + ); + } ); - $params.document_width = Math.max( - $params.body.scrollWidth, - $params.body.offsetWidth, - $params.html.clientWidth, - $params.html.scrollWidth, - $params.html.offsetWidth + $params.element.bind( + 'touchend touchcancel mouseup', function () { + $timeout.cancel($params.dragTimer); + } ); } - function _fnDragMove(e, $params) { + function _fnKeydownHandler(e, $params) { var _$scope = $params.$scope; - if (!$params.dragStarted) { - if (!$params.dragDelaying) { - $params.dragStarted = true; - _$scope.$safeApply( - function () { - _$scope.$callbacks.dragStart($params.dragInfo); - } - ); + if (e.keyCode === 27) { + if (_$scope.enabledStatus) { + _$scope.hideStatus(); } - return; - } - if ($params.dragElm) { - e.preventDefault(); - if ($params.$window.getSelection) { - $params.$window.getSelection().removeAllRanges(); - } else if ($params.$window.document.selection) { - $params.$window.document.selection.empty(); - } + _$scope.$$apply = false; + _fnDragEnd(e, $params); + } else { + if (_$scope.enabledHotkey && e.shiftKey) { + _$scope.enableMove(true); + if (_$scope.enabledStatus) { + _$scope.refreshStatus(); + } - var eventObj = $TreeDnDHelper.eventObj(e), - leftElmPos = eventObj.pageX - $params.pos.offsetX, - topElmPos = eventObj.pageY - $params.pos.offsetY; + if (!$params.dragInfo) { + return; + } - //dragElm can't leave the screen on the left - if (leftElmPos < 0) { - leftElmPos = 0; - } + var _scope = _$scope.getScope($params.dragInfo.node), + _element = _scope.$element; - //dragElm can't leave the screen on the top - if (topElmPos < 0) { - topElmPos = 0; - } + if (_scope.isTable) { + _$scope.for_all_descendants( + $params.dragInfo.node, function (_node, _parent) { + _scope = _$scope.getScope(_node); + _element = _scope && _scope.$element; + if (_scope && _element && (!_parent && _node.__visible__ || _parent.__expanded__)) { + if (_$scope.$class.hidden) { + _element.addClass(_$scope.$class.hidden); + } + } + return _node.__visible__ === false || _node.__expanded__ === false - //dragElm can't leave the screen on the bottom - if (topElmPos + 10 > $params.document_height) { - topElmPos = $params.document_height - 10; + }, null, true + ); + } else { + if (_$scope.$class.hidden) { + _element.addClass(_$scope.$class.hidden); + } + } } + } + } - //dragElm can't leave the screen on the right - if (leftElmPos + 10 > $params.document_width) { - leftElmPos = $params.document_width - 10; - } - - $params.dragElm.css( - { - 'left': leftElmPos + _$scope.$callbacks.calsIndent( - $params.offsetEdge + 1, - true, - true - ) + 'px', - 'top': topElmPos + 'px' - } - ); + function _fnKeyupHandler(e, $params) { + var _$scope = $params.$scope; + if (_$scope.enabledHotkey && !e.shiftKey) { + _$scope.enableMove(false); if (_$scope.enabledStatus) { - _$scope.setPositionStatus(e); + _$scope.refreshStatus(); } - var top_scroll = window.pageYOffset || $params.$window.document.documentElement.scrollTop, - bottom_scroll = top_scroll + (window.innerHeight || $params.$window.document.clientHeight || $params.$window.document.clientHeight); - // to scroll down if cursor y-position is greater than the bottom position the vertical scroll - if (bottom_scroll < eventObj.pageY && bottom_scroll <= $params.document_height) { - window.scrollBy(0, 10); - } - // to scroll top if cursor y-position is less than the top position the vertical scroll - if (top_scroll > eventObj.pageY) { - window.scrollBy(0, -10); + if (!$params.dragInfo) { + return; } - $TreeDnDHelper.positionMoved(e, $params.pos, $params.firstMoving); + var _scope = _$scope.getScope($params.dragInfo.node), + _element = _scope.$element; - if ($params.firstMoving) { - $params.firstMoving = false; - return; + if (_scope.isTable) { + _$scope.for_all_descendants( + $params.dragInfo.node, function (_node, _parent) { + _scope = _$scope.getScope(_node); + _element = _scope && _scope.$element; + if (_scope && _element && (!_parent && _node.__visible__ || _parent.__expanded__)) { + if (_$scope.$class.hidden) { + _element.removeClass(_$scope.$class.hidden); + } + } + return _node.__visible__ === false || _node.__expanded__ === false + }, null, true + ); + } else { + if (_$scope.$class.hidden) { + _element.removeClass(_$scope.$class.hidden); + } } - // check if add it as a child node first + } + } - var targetX = eventObj.pageX - $params.$window.document.body.scrollLeft, - targetY = eventObj.pageY - (window.pageYOffset || $params.$window.document.documentElement.scrollTop), + function _$init(scope, element, $window, $document) { + var $params = { + hasTouch: 'ontouchstart' in window, + firstMoving: null, + dragInfo: null, + pos: null, + placeElm: null, + dragElm: null, + dragDelaying: true, + dragStarted: false, + dragTimer: null, + body: document.body, + html: document.documentElement, + document_height: null, + document_width: null, + offsetEdge: null, + $scope: scope, + $window: $window, + $document: $document, + element: element, + bindDrag: function () { + _fnBindDrag($params); + }, + dragEnd: function (e) { + _fnDragEnd(e, $params); + }, + dragMoveEvent: function (e) { + _fnDragMove(e, $params); + }, + dragEndEvent: function (e) { + scope.$$apply = true; + _fnDragEnd(e, $params); + }, + dragCancelEvent: function (e) { + _fnDragEnd(e, $params); + } + }, + keydownHandler = function (e) { + return _fnKeydownHandler(e, $params); + }, + keyupHandler = function (e) { + return _fnKeyupHandler(e, $params); + }; - targetElm, - targetScope, - targetBefore, - targetOffset, - isChanged = true, - isVeritcal = true, - isEmpty, - isSwapped, - _scope, - _target, - _parent, - _info = $params.dragInfo, - _move = _info.move, - _drag = _info.node, - _drop = _info.drop, - treeScope = _info.target, - fnSwapTree, - isHolder = _fnPlaceHolder(e, $params); + scope.dragEnd = function (e) { + $params.dragEnd(e); + }; - if (!isHolder) { - /* when using elementFromPoint() inside an iframe, you have to call - elementFromPoint() twice to make sure IE8 returns the correct value - $params.$window.document.elementFromPoint(targetX, targetY);*/ + $params.bindDrag(); - targetElm = angular.element( - $params.$window.document.elementFromPoint( - targetX, - targetY - ) - ); + angular.element($window.document.body).bind('keydown', keydownHandler); + angular.element($window.document.body).bind('keyup', keyupHandler); + //unbind handler that retains scope + scope.$on( + '$destroy', function () { + // Unbind keyboard handlers + angular.element($window.document.body).unbind('keydown', keydownHandler); + angular.element($window.document.body).unbind('keyup', keyupHandler); - targetScope = targetElm.scope(); - if (!targetScope || !targetScope.$callbacks || !targetScope.$callbacks.droppable()) { - // Not allowed Drop Item - return; - } + // Unbind element drag listeners + $params.element.unbind('touchstart mousedown'); + $params.element.unbind('touchend touchcancel mouseup'); - fnSwapTree = function () { - treeScope = targetScope.getScopeTree(); - _target = _info.target; + // Unbind any active document listeners (in case drag is in progress) + _fnUnbindDocumentListeners($params); - if (_info.target !== treeScope) { - // Replace by place-holder new - _target.hidePlace(); - _target.targeting = false; - treeScope.targeting = true; + // Cancel any pending drag timer + if ($params.dragTimer) { + $timeout.cancel($params.dragTimer); + $params.dragTimer = null; + } - _info.target = treeScope; - $params.placeElm = treeScope.initPlace(targetScope.$element, $params.dragElm); + // Remove and clean up drag element if still present + if ($params.dragElm) { + $params.dragElm.remove(); + $params.dragElm = null; + } - _target = null; - isSwapped = true; - } - return true; - }; + // Clean up status element + if (scope.statusElm) { + scope.statusElm.remove(); + scope.statusElm = null; + } - if (angular.isFunction(targetScope.getScopeNode)) { - targetScope = targetScope.getScopeNode(); - if (!fnSwapTree()) { - return; - } - } else { - if (targetScope.$type === 'TreeDnDNodes' || targetScope.$type === 'TreeDnD') { - if (targetScope.tree_nodes) { - if (targetScope.tree_nodes.length === 0) { - if (!fnSwapTree()) { - return; - } - // Empty - isEmpty = true; - } - } else { - return; - } - } else { - return; - } + // Clean up placeholder element + if (scope.placeElm) { + scope.placeElm.remove(); + scope.placeElm = null; } - } - if ($params.pos.dirAx && !isSwapped || isHolder) { - isVeritcal = false; - targetScope = _info.scope; - } + // Clear dragInfo to break circular references + $params.dragInfo = null; + $params.pos = null; + $params.placeElm = null; - if (!targetScope.$element && !targetScope) { - return; + // Clear $params scope reference + $params.$scope = null; + $params.element = null; } + ); + } - if (isEmpty) { - _move.parent = null; - _move.pos = 0; - - _drop = null; - } else { - // move vertical - if (isVeritcal) { - targetElm = targetScope.$element; // Get the element of tree-dnd-node - if (angular.isUndefinedOrNull(targetElm)) { - return; - } - targetOffset = $TreeDnDHelper.offset(targetElm); + return _$init; + } + ]); - if (targetScope.horizontal && !targetScope.isTable) { - targetBefore = eventObj.pageX < targetOffset.left + $TreeDnDHelper.width(targetElm) / 2; - } else { - if (targetScope.isTable) { - targetBefore = eventObj.pageY < targetOffset.top + $TreeDnDHelper.height(targetElm) / 2; - } else { - var _height = $TreeDnDHelper.height(targetElm); +angular.module('ntt.TreeDnD') + .factory('$TreeDnDControl', function () { + var _target, _parent, + i, len; - if (targetScope.getElementChilds()) { - _height -= -$TreeDnDHelper.height(targetScope.getElementChilds()); - } + function fnSetCollapse(node) { + node.__expanded__ = false; + } - if (eventObj.pageY > targetOffset.top + _height) { - return; - } + function fnSetExpand(node) { + node.__expanded__ = true; + } - targetBefore = eventObj.pageY < targetOffset.top + _height / 2; - } - } + function _$init(scope) { + var n, tree = { + selected_node: null, + for_all_descendants: scope.for_all_descendants, + select_node: function (node) { + if (!node) { + if (tree.selected_node) { + delete tree.selected_node.__selected__; + } + tree.selected_node = null; + return null; + } - if (!angular.isFunction(targetScope.getData)) { - return; - } + if (node !== tree.selected_node) { + if (tree.selected_node) { + delete tree.selected_node.__selected__; + } + node.__selected__ = true; + tree.selected_node = node; + tree.expand_all_parents(node); + if (angular.isFunction(tree.on_select)) { + tree.on_select(node); + } + } - _target = targetScope.getData(); - _parent = targetScope.getNode(_target.__parent_real__); + return node; + }, + deselect_node: function () { + _target = null; + if (tree.selected_node) { + delete tree.selected_node.__selected__; + _target = tree.selected_node; + tree.selected_node = null; + } + return _target; + }, + get_parent: function (node) { + node = node || tree.selected_node; - if (targetBefore) { - var _prev = targetScope.getPrevSibling(_target); + if (node && node.__parent_real__ !== null) { + return scope.tree_nodes[node.__parent_real__]; + } + return null; + }, + for_all_ancestors: function (node, fn) { + _parent = tree.get_parent(node); + if (_parent) { + if (fn(_parent)) { + return false; + } - _move.parent = _parent; - _move.pos = angular.isDefined(_prev) ? _prev.__index__ + 1 : 0; + return tree.for_all_ancestors(_parent, fn); + } + return true; + }, + expand_all_parents: function (node) { + node = node || tree.selected_node; - _drop = _prev; - } else { - if (_target.__expanded__ && !(_target.__children__.length === 1 && _target.__index_real__ === _drag.__parent_real__)) { - _move.parent = _target; - _move.pos = 0; + if (angular.isObject(node)) { + tree.for_all_ancestors(node, fnSetExpand); + } + }, + collapse_all_parents: function (node) { + node = node || tree.selected_node; + if (angular.isObject(node)) { + tree.for_all_ancestors(node, fnSetCollapse); + } + }, - _drop = null; - } else { - _move.parent = _parent; - _move.pos = _target.__index__ + 1; + reload_data: function () { + return scope.reload_data(); + }, + add_node: function (parent, new_node, index) { + if (typeof index !== 'number') { + if (parent) { + parent.__children__.push(new_node); + parent.__expanded__ = true; + } else { + scope.treeData.push(new_node); + } + } else { + if (parent) { + parent.__children__.splice(index, 0, new_node); + parent.__expanded__ = true; + } else { + scope.treeData.splice(index, 0, new_node); + } + } + return new_node; + }, + add_node_root: function (new_node) { + tree.add_node(null, new_node); + return new_node; + }, + expand_all: function () { + len = scope.treeData.length; + for (i = 0; i < len; i++) { + tree.for_all_descendants(scope.treeData[i], fnSetExpand); + } + }, + collapse_all: function () { + len = scope.treeData.length; + for (i = 0; i < len; i++) { + tree.for_all_descendants(scope.treeData[i], fnSetCollapse); + } + }, + remove_node: function (node) { + node = node || tree.selected_node; - _drop = _target; - } - } + if (angular.isObject(node)) { + if (node.__parent_real__ !== null) { + _parent = tree.get_parent(node).__children__; } else { - // move horizontal - if ($params.pos.dirAx && $params.pos.distAxX >= treeScope.dragBorder) { - $params.pos.distAxX = 0; - // increase horizontal level if previous sibling exists and is not collapsed - if ($params.pos.distX > 0) { - _parent = _drop; - if (!_parent) { - if (_move.pos - 1 >= 0) { - _parent = _move.parent.__children__[_move.pos - 1]; - } else { - return; - } - } + _parent = scope.treeData; + } - if (_info.drag === _info.target && _parent === _drag && _$scope.enabledMove) { - _parent = treeScope.getPrevSibling(_parent); - } + _parent.splice(node.__index__, 1); - if (_parent && _parent.__visible__) { - var _len = _parent.__children__.length; + tree.reload_data(); - _move.parent = _parent; - _move.pos = _len; + if (tree.selected_node === node) { + tree.selected_node = null; + } + } + }, + expand_node: function (node) { + node = node || tree.selected_node; - if (_len > 0) { - _drop = _parent.__children__[_len - 1]; - } else { - _drop = null; - } - } else { - // Not changed - return; - } - } else if ($params.pos.distX < 0) { - _target = _move.parent; - if (_target && - (_target.__children__.length === 0 || - _target.__children__.length - 1 < _move.pos || - _info.drag === _info.target && - _target.__index_real__ === _drag.__parent_real__ && - _target.__children__.length - 1 === _drag.__index__ && _$scope.enabledMove) - ) { - _parent = treeScope.getNode(_target.__parent_real__); + if (angular.isObject(node)) { + node.__expanded__ = true; + return node; + } + }, + collapse_node: function (node) { + node = node || tree.selected_node; - _move.parent = _parent; - _move.pos = _target.__index__ + 1; + if (angular.isObject(node)) { + node.__expanded__ = false; + return node; + } + }, + get_selected_node: function () { + return tree.selected_node; + }, + get_first_node: function () { + len = scope.treeData.length; + if (len > 0) { + return scope.treeData[0]; + } - _drop = _target; - } else { - // Not changed - return; - } - } else { - return; - } - } else { - // limited - return; - } + return null; + }, + get_children: function (node) { + node = node || tree.selected_node; + + return node.__children__; + }, + get_siblings: function (node) { + node = node || tree.selected_node; + if (angular.isObject(node)) { + _parent = tree.get_parent(node); + if (_parent) { + _target = _parent.__children__; + } else { + _target = scope.treeData; + } + return _target; + } + }, + get_next_sibling: function (node) { + node = node || tree.selected_node; + if (angular.isObject(node)) { + _target = tree.get_siblings(node); + n = _target.length; + if (node.__index__ < n) { + return _target[node.__index__ + 1]; + } + } + }, + get_prev_sibling: function (node) { + node = node || tree.selected_node; + _target = tree.get_siblings(node); + if (node.__index__ > 0) { + return _target[node.__index__ - 1]; + } + }, + get_first_child: function (node) { + node = node || tree.selected_node; + if (angular.isObject(node)) { + _target = node.__children__; + if (_target && _target.length > 0) { + return node.__children__[0]; } } + return null; + }, + get_closest_ancestor_next_sibling: function (node) { + node = node || tree.selected_node; + _target = tree.get_next_sibling(node); + if (_target) { + return _target; + } - if (_info.drag === _info.target && - _move.parent && - _drag.__parent_real__ === _move.parent.__index_real__ && - _drag.__index__ === _move.pos - ) { - isChanged = false; + _parent = tree.get_parent(node); + if (_parent) { + return tree.get_closest_ancestor_next_sibling(_parent); } - if (treeScope.$callbacks.accept(_info, _move, isChanged)) { - _info.move = _move; - _info.drop = _drop; - _info.changed = isChanged; - _info.scope = targetScope; + return null; + }, + get_next_node: function (node) { + node = node || tree.selected_node; - if (targetScope.isTable) { - $TreeDnDHelper.replaceIndent( - treeScope, - $params.placeElm, - angular.isUndefinedOrNull(_move.parent) ? 1 : _move.parent.__level__ + 1 - ); + if (angular.isObject(node)) { + _target = tree.get_first_child(node); + if (_target) { + return _target; + } else { + return tree.get_closest_ancestor_next_sibling(node); + } + } + }, + get_prev_node: function (node) { + node = node || tree.selected_node; + + if (angular.isObject(node)) { + _target = tree.get_prev_sibling(node); + if (_target) { + return tree.get_last_descendant(_target); + } + + _parent = tree.get_parent(node); + return _parent; + } + }, + get_last_descendant: scope.getLastDescendant, + select_parent_node: function (node) { + node = node || tree.selected_node; + + if (angular.isObject(node)) { + _parent = tree.get_parent(node); + if (_parent) { + return tree.select_node(_parent); + } + } + }, + select_first_node: function () { + var firstNode = tree.get_first_node(); + return tree.select_node(firstNode); + }, + select_next_sibling: function (node) { + node = node || tree.selected_node; + + if (angular.isObject(node)) { + _target = tree.get_next_sibling(node); + if (_target) { + return tree.select_node(_target); + } + } + }, + select_prev_sibling: function (node) { + node = node || tree.selected_node; + + if (angular.isObject(node)) { + _target = tree.get_prev_sibling(node); + if (_target) { + return tree.select_node(_target); + } + } + }, + select_next_node: function (node) { + node = node || tree.selected_node; + + if (angular.isObject(node)) { + _target = tree.get_next_node(node); + if (_target) { + return tree.select_node(_target); + } + } + }, + select_prev_node: function (node) { + node = node || tree.selected_node; + + if (angular.isObject(node)) { + _target = tree.get_prev_node(node); + if (_target) { + return tree.select_node(_target); + } + } + } + }; + angular.extend(scope.tree, tree); + return scope.tree; + } + + return _$init; + }); + +angular.module('ntt.TreeDnD') + .factory('$TreeDnDFilter', [ + '$filter', function ($filter) { + return fnInitFilter; + + function for_all_descendants(options, node, fieldChild, fnBefore, fnAfter, parentPassed) { + if (!angular.isFunction(fnBefore)) { + return null; + } + + var _i, _len, _nodes, + _nodePassed = fnBefore(options, node), + _childPassed = false, + _filter_index = options.filter_index; + + if (angular.isDefined(node[fieldChild])) { + _nodes = node[fieldChild]; + _len = _nodes.length; + + options.filter_index = 0; + for (_i = 0; _i < _len; _i++) { + _childPassed = for_all_descendants( + options, + _nodes[_i], + fieldChild, + fnBefore, + fnAfter, + _nodePassed || parentPassed + ) || _childPassed; + } + + // restore filter_index of node + options.filter_index = _filter_index; + } + + if (angular.isFunction(fnAfter)) { + fnAfter(options, node, _nodePassed === true, _childPassed === true, parentPassed === true); + } + + return _nodePassed || _childPassed; + } + + /** + * Check data with callback + * @param {string|object|function|regex} callback + * @param {*} data + * @returns {null|boolean} + * @private + */ + function _fnCheck(callback, data) { + if (angular.isUndefinedOrNull(data) || angular.isArray(data)) { + return null; + } + + if (angular.isFunction(callback)) { + return callback(data, $filter); + } else { + if (typeof callback === 'boolean') { + data = !!data; + return data === callback; + } else if (angular.isDefined(callback)) { + try { + var _regex = new RegExp(callback); + return _regex.test(data); + } + catch (err) { + if (typeof data === 'string') { + return data.indexOf(callback) > -1; + } else { + return null; + } + } + } else { + return null; + } + } + } + + /** + * `fnProcess` to call `_fnCheck`. If `condition` is `array` then call `for_each_filter` + * else will call `_fnCheck`. Specical `condition.field` is `_$` then apply `condition.callback` for all field, if have `field` invaild then `return true`. + * + * @param node + * @param condition + * @param isAnd + * @returns {null|boolean} + * @private + */ + function _fnProccess(node, condition, isAnd) { + if (angular.isArray(condition)) { + return for_each_filter(node, condition, isAnd); + } else { + var _key = condition.field, + _callback = condition.callback, + _iO, _keysO, _lenO; + + if (_key === '_$') { + _keysO = Object.keys(node); + _lenO = _keysO.length; + for (_iO = 0; _iO < _lenO; _iO++) { + if (_fnCheck(_callback, node[_keysO[_iO]])) { + return true; + } + } + } else if (angular.isDefined(node[_key])) { + return _fnCheck(_callback, node[_key]); + } + } + return null; + } + + /** + * + * @param {object} node + * @param {array} conditions Array `conditions` + * @param {boolean} isAnd check with condition `And`, if `And` then `return false` when all `false` + * @returns {null|boolean} + */ + function for_each_filter(node, conditions, isAnd) { + var i, len = conditions.length || 0, passed = false; + if (len === 0) { + return null; + } + + for (i = 0; i < len; i++) { + if (_fnProccess(node, conditions[i], !isAnd)) { + passed = true; + // if condition `or` then return; + if (!isAnd) { + return true; + } + } else { + + // if condition `and` and result in fnProccess = false then return; + if (isAnd) { + return false; + } + } + } + + return passed; + } + + /** + * Will call _fnAfter to clear data no need + * @param {object} options + * @param {object} node + * @param {boolean} isNodePassed + * @param {boolean} isChildPassed + * @param {boolean} isParentPassed + * @private + */ + function _fnAfter(options, node, isNodePassed, isChildPassed, isParentPassed) { + if (isNodePassed === true) { + node.__filtered__ = true; + node.__filtered_visible__ = true; + node.__filtered_index__ = options.filter_index++; + return; //jmp + } else if (isChildPassed === true && options.showParent === true + || isParentPassed === true && options.showChild === true) { + node.__filtered__ = false; + node.__filtered_visible__ = true; + node.__filtered_index__ = options.filter_index++; + return; //jmp + } + + // remove attr __filtered__ + delete node.__filtered__; + delete node.__filtered_visible__; + delete node.__filtered_index__; + } + + /** + * `fnBefore` will called when `for_all_descendants` of `node` checking. + * If `filter` empty then return `true` else result of function `_fnProccess` {@see _fnProccess} + * + * @param {object} options + * @param {object} node + * @returns {null|boolean} + * @private + */ + function _fnBefore(options, node) { + if (options.filter.length === 0) { + return true; + } else { + return _fnProccess(node, options.filter, options.beginAnd || false); + } + } + + /** + * `fnBeforeClear` will called when `for_all_descendants` of `node` checking. + * Alway false to Clear Filter empty + * + * @param {object} options + * @param {object} node + * @returns {null|boolean} + * @private + */ + function _fnBeforeClear(options, node) { + return false; + } - if (_drop) { - _parent = (_move.parent ? _move.parent.__children__ : null) || _info.target.treeData; + /** + * `_fnConvert` to convert `filter` `object` to `array` invaild. + * + * @param {object|array} filters + * @returns {array} Instead of `filter` or new array invaild *(converted from filter)* + * @private + */ + function _fnConvert(filters) { + var _iF, _lenF, _keysF, + _filter, + _state; + // convert filter object to array filter + if (angular.isObject(filters) && !angular.isArray(filters)) { + _keysF = Object.keys(filters); + _lenF = _keysF.length; + _filter = []; - if (_drop.__index__ < _parent.length - 1) { - // Find fast - _drop = _parent[_drop.__index__ + 1]; - _scope = _info.target.getScope(_drop); - _scope.$element[0].parentNode.insertBefore( - $params.placeElm[0], - _scope.$element[0] - ); - } else { - _target = _info.target.getLastDescendant(_drop); - _scope = _info.target.getScope(_target); - _scope.$element.after($params.placeElm); - } - } else { - _scope = _info.target.getScope(_move.parent); - if (_scope) { - if (_move.parent) { - _scope.$element.after($params.placeElm); + if (_lenF > 0) { + for (_iF = 0; _iF < _lenF; _iF++) { - } else { - _scope.getElementChilds().prepend($params.placeElm); - } - } - } - } else { - _scope = _info.target.getScope(_drop || _move.parent); - if (_drop) { - _scope.$element.after($params.placeElm); + if (typeof filters[_keysF[_iF]] === 'string' && filters[_keysF[_iF]].length === 0) { + continue; + } else if (angular.isArray(filters[_keysF[_iF]])) { + _state = filters[_keysF[_iF]]; + } else if (angular.isObject(filters[_keysF[_iF]])) { + _state = _fnConvert(filters[_keysF[_iF]]); } else { - _scope.getElementChilds().prepend($params.placeElm); + _state = { + field: _keysF[_iF], + callback: filters[_keysF[_iF]] + }; } + _filter.push(_state); } + } + _state = null; + return _filter; + } + else { + return filters; + } + } - treeScope.showPlace(); + /** + * `fnInitFilter` function is constructor of service `$TreeDnDFilter`. + * @constructor + * @param {object|array} treeData + * @param {object|array} filters + * @param {object} options + * @param {string} keyChild + * @returns {array} Return `treeData` or `treeData` with `filter` + * @private + */ + function fnInitFilter(treeData, filters, options, keyChild) { + if (!angular.isArray(treeData) + || treeData.length === 0) { + return treeData; + } - _$scope.$safeApply( - function () { - _$scope.$callbacks.dragMove(_info); - } + var _i, _len, + _filter; + + _filter = _fnConvert(filters); + if (!(angular.isArray(_filter) || angular.isObject(_filter)) + || _filter.length === 0) { + for (_i = 0, _len = treeData.length; _i < _len; _i++) { + for_all_descendants( + options, + treeData[_i], + keyChild || '__children__', + _fnBeforeClear, _fnAfter ); } + return treeData; + } + options.filter = _filter; + options.filter_index = 0; + for (_i = 0, _len = treeData.length; _i < _len; _i++) { + for_all_descendants( + options, + treeData[_i], + keyChild || '__children__', + _fnBefore, _fnAfter + ); } + + return treeData; } - function _fnDragEnd(e, $params) { - e.preventDefault(); - if ($params.dragElm) { - var _passed = false, - _$scope = $params.$scope, - _scope = _$scope.getScope($params.dragInfo.node), - _element = _scope.$element; + }] + ); - _$scope.$safeApply( - function () { - _passed = _$scope.$callbacks.beforeDrop($params.dragInfo); - } - ); +angular.module('ntt.TreeDnD') + .factory('$TreeDnDOrderBy', [ + '$filter', + function ($filter) { + var _fnOrderBy = $filter('orderBy'), + for_all_descendants = function for_all_descendants(options, node, name, fnOrderBy) { + var _i, _len, _nodes; - // rollback all - if (_scope.isTable) { - _$scope.for_all_descendants( - $params.dragInfo.node, function (_node, _parent) { - _scope = _$scope.getScope(_node); - _element = _scope && _scope.$element; - if (_scope && _element && (!_parent && _node.__visible__ || _parent.__expanded__)) { - if (_$scope.$class.hidden) { - _element.removeClass(_$scope.$class.hidden); - } - } - return _node.__visible__ === false || _node.__expanded__ === false - }, null, true - ); - } else { - if (_$scope.$class.hidden) { - _element.removeClass(_$scope.$class.hidden); + if (angular.isDefined(node[name])) { + _nodes = node[name]; + _len = _nodes.length; + // OrderBy children + for (_i = 0; _i < _len; _i++) { + _nodes[_i] = for_all_descendants(options, _nodes[_i], name, fnOrderBy); } + + node[name] = fnOrderBy(node[name], options); + } + return node; + }, + _fnOrder = function _fnOrder(list, orderBy) { + return _fnOrderBy(list, orderBy); + }, + _fnMain = function _fnMain(treeData, orderBy) { + if (!angular.isArray(treeData) + || treeData.length === 0 + || !(angular.isArray(orderBy) || angular.isObject(orderBy) || angular.isString(orderBy) || angular.isFunction(orderBy)) + || orderBy.length === 0 && !angular.isFunction(orderBy)) { + return treeData; } - $params.dragElm.remove(); - $params.dragElm = null; + var _i, _len; - if (_$scope.enabledStatus) { - _$scope.hideStatus(); + for (_i = 0, _len = treeData.length; _i < _len; _i++) { + treeData[_i] = for_all_descendants( + orderBy, + treeData[_i], + '__children__', + _fnOrder + ); } - if (_$scope.$$apply) { - _$scope.$safeApply( - function () { - var _status = _$scope.$callbacks.dropped( - $params.dragInfo, - _passed - ); + return _fnOrder(treeData, orderBy); + }; - _$scope.$callbacks.dragStop($params.dragInfo, _status); - clearData(); + return _fnMain; + }] + ); + +angular.module('ntt.TreeDnD') + .factory('$TreeDnDConvert', function () { + var _$initConvert = { + line2tree: function (data, primaryKey, parentKey, callback) { + callback = typeof callback === 'function' ? callback : function () { + }; + if (!data || data.length === 0 || !primaryKey || !parentKey) { + return []; + } + var tree = [], + rootIds = [], + item = data[0], + _primary = item[primaryKey], + treeObjs = {}, + parentId, parent, + len = data.length, + i = 0; + + while (i < len) { + item = data[i++]; + callback(item); + _primary = item[primaryKey]; + treeObjs[_primary] = item; + } + i = 0; + while (i < len) { + item = data[i++]; + callback(item); + _primary = item[primaryKey]; + treeObjs[_primary] = item; + parentId = item[parentKey]; + if (parentId) { + parent = treeObjs[parentId]; + if (parent) { + if (parent.__children__) { + parent.__children__.push(item); + } else { + parent.__children__ = [item]; } - ); + } } else { - _fnBindDrag($params); - _$scope.$safeApply( - function () { - _$scope.$callbacks.dragStop($params.dragInfo, false); - clearData(); - } - ); + rootIds.push(_primary); } - } - - function clearData() { - $params.dragInfo.target.hidePlace(); - $params.dragInfo.target.targeting = false; - - $params.dragInfo = null; - _$scope.$$apply = false; - _$scope.setDragging(null); + len = rootIds.length; + for (i = 0; i < len; i++) { + tree.push(treeObjs[rootIds[i]]); } - - angular.element($params.$document).unbind('touchend', $params.dragEndEvent); // Mobile - angular.element($params.$document).unbind('touchcancel', $params.dragEndEvent); // Mobile - angular.element($params.$document).unbind('touchmove', $params.dragMoveEvent); // Mobile - angular.element($params.$document).unbind('mouseup', $params.dragEndEvent); - angular.element($params.$document).unbind('mousemove', $params.dragMoveEvent); - angular.element($params.$window.document.body).unbind('mouseleave', $params.dragCancelEvent); - } - - function _fnDragStartEvent(e, $params) { - if ($params.$scope.$callbacks.draggable()) { - _fnDragStart(e, $params); + return tree; + }, + tree2tree: function access_child(data, containKey, callback) { + callback = typeof callback === 'function' ? callback : function () { + }; + var _tree = [], + _i, + _len = data ? data.length : 0, + _copy, _child; + for (_i = 0; _i < _len; _i++) { + _copy = angular.copy(data[_i]); + callback(_copy); + if (angular.isArray(_copy[containKey]) && _copy[containKey].length > 0) { + _child = access_child(_copy[containKey], containKey, callback); + delete _copy[containKey]; + _copy.__children__ = _child; + } + _tree.push(_copy); } + return _tree; } + }; - function _fnBindDrag($params) { - $params.element.bind( - 'touchstart mousedown', function (e) { - $params.dragDelaying = true; - $params.dragStarted = false; - _fnDragStartEvent(e, $params); - $params.dragTimer = $timeout( - function () { - $params.dragDelaying = false; - }, $params.$scope.dragDelay - ); - } - ); - - $params.element.bind( - 'touchend touchcancel mouseup', function () { - $timeout.cancel($params.dragTimer); - } - ); - } + return _$initConvert; + }); - function _fnKeydownHandler(e, $params) { - var _$scope = $params.$scope; - if (e.keyCode === 27) { - if (_$scope.enabledStatus) { - _$scope.hideStatus(); +angular.module('ntt.TreeDnD') + .factory('$TreeDnDHelper', [ + '$document', '$window', + function ($document, $window) { + var _$helper = { + nodrag: function (targetElm) { + return typeof targetElm.attr('data-nodrag') !== 'undefined'; + }, + eventObj: function (e) { + var obj = e; + if (e.targetTouches !== undefined) { + obj = e.targetTouches.item(0); + } else if (e.originalEvent !== undefined && e.originalEvent.targetTouches !== undefined) { + obj = e.originalEvent.targetTouches.item(0); } + return obj; + }, + dragInfo: function (scope) { + var _node = scope.getData(), + _tree = scope.getScopeTree(), + _parent = scope.getNode(_node.__parent_real__); - _$scope.$$apply = false; - _fnDragEnd(e, $params); - } else { - if (_$scope.enabledHotkey && e.shiftKey) { - _$scope.enableMove(true); - if (_$scope.enabledStatus) { - _$scope.refreshStatus(); - } - - if (!$params.dragInfo) { - return; - } + return { + node: _node, + parent: _parent, + move: { + parent: _parent, + pos: _node.__index__ + }, + scope: scope, + target: _tree, + drag: _tree, + drop: scope.getPrevSibling(_node), + changed: false + }; + }, + height: function (element) { + return element.prop('scrollHeight'); + }, + width: function (element) { + return element.prop('scrollWidth'); + }, + offset: function (element) { + var boundingClientRect = element[0].getBoundingClientRect(); + return { + width: element.prop('offsetWidth'), + height: element.prop('offsetHeight'), + top: boundingClientRect.top + ($window.pageYOffset || $document[0].body.scrollTop || $document[0].documentElement.scrollTop), + left: boundingClientRect.left + ($window.pageXOffset || $document[0].body.scrollLeft || $document[0].documentElement.scrollLeft) + }; + }, + positionStarted: function (e, target) { + return { + offsetX: e.pageX - this.offset(target).left, + offsetY: e.pageY - this.offset(target).top, + startX: e.pageX, + lastX: e.pageX, + startY: e.pageY, + lastY: e.pageY, + nowX: 0, + nowY: 0, + distX: 0, + distY: 0, + dirAx: 0, + dirX: 0, + dirY: 0, + lastDirX: 0, + lastDirY: 0, + distAxX: 0, + distAxY: 0 + }; + }, + positionMoved: function (e, pos, firstMoving) { + // mouse position last events + pos.lastX = pos.nowX; + pos.lastY = pos.nowY; - var _scope = _$scope.getScope($params.dragInfo.node), - _element = _scope.$element; + // mouse position this events + pos.nowX = e.pageX; + pos.nowY = e.pageY; - if (_scope.isTable) { - _$scope.for_all_descendants( - $params.dragInfo.node, function (_node, _parent) { - _scope = _$scope.getScope(_node); - _element = _scope && _scope.$element; - if (_scope && _element && (!_parent && _node.__visible__ || _parent.__expanded__)) { - if (_$scope.$class.hidden) { - _element.addClass(_$scope.$class.hidden); - } - } - return _node.__visible__ === false || _node.__expanded__ === false + // distance mouse moved between events + pos.distX = pos.nowX - pos.lastX; + pos.distY = pos.nowY - pos.lastY; - }, null, true - ); - } else { - if (_$scope.$class.hidden) { - _element.addClass(_$scope.$class.hidden); - } - } - } - } - } + // direction mouse was moving + pos.lastDirX = pos.dirX; + pos.lastDirY = pos.dirY; - function _fnKeyupHandler(e, $params) { - var _$scope = $params.$scope; - if (_$scope.enabledHotkey && !e.shiftKey) { - _$scope.enableMove(false); + // direction mouse is now moving (on both axis) + pos.dirX = pos.distX === 0 ? 0 : pos.distX > 0 ? 1 : -1; + pos.dirY = pos.distY === 0 ? 0 : pos.distY > 0 ? 1 : -1; - if (_$scope.enabledStatus) { - _$scope.refreshStatus(); - } + // axis mouse is now moving on + var newAx = Math.abs(pos.distX) > Math.abs(pos.distY) ? 1 : 0; - if (!$params.dragInfo) { + // do nothing on first move + if (firstMoving) { + pos.dirAx = newAx; + pos.moving = true; return; } - var _scope = _$scope.getScope($params.dragInfo.node), - _element = _scope.$element; - - if (_scope.isTable) { - _$scope.for_all_descendants( - $params.dragInfo.node, function (_node, _parent) { - _scope = _$scope.getScope(_node); - _element = _scope && _scope.$element; - if (_scope && _element && (!_parent && _node.__visible__ || _parent.__expanded__)) { - if (_$scope.$class.hidden) { - _element.removeClass(_$scope.$class.hidden); - } - } - return _node.__visible__ === false || _node.__expanded__ === false - }, null, true - ); + // calc distance moved on this axis (and direction) + if (pos.dirAx !== newAx) { + pos.distAxX = 0; + pos.distAxY = 0; } else { - if (_$scope.$class.hidden) { - _element.removeClass(_$scope.$class.hidden); + pos.distAxX += Math.abs(pos.distX); + if (pos.dirX !== 0 && pos.dirX !== pos.lastDirX) { + pos.distAxX = 0; } - } - } - } - - function _$init(scope, element, $window, $document) { - var $params = { - hasTouch: 'ontouchstart' in window, - firstMoving: null, - dragInfo: null, - pos: null, - placeElm: null, - dragElm: null, - dragDelaying: true, - dragStarted: false, - dragTimer: null, - body: document.body, - html: document.documentElement, - document_height: null, - document_width: null, - offsetEdge: null, - $scope: scope, - $window: $window, - $document: $document, - element: element, - bindDrag: function () { - _fnBindDrag($params); - }, - dragEnd: function (e) { - _fnDragEnd(e, $params); - }, - dragMoveEvent: function (e) { - _fnDragMove(e, $params); - }, - dragEndEvent: function (e) { - scope.$$apply = true; - _fnDragEnd(e, $params); - }, - dragCancelEvent: function (e) { - _fnDragEnd(e, $params); + pos.distAxY += Math.abs(pos.distY); + if (pos.dirY !== 0 && pos.dirY !== pos.lastDirY) { + pos.distAxY = 0; } - }, - keydownHandler = function (e) { - return _fnKeydownHandler(e, $params); - }, - keyupHandler = function (e) { - return _fnKeyupHandler(e, $params); - }; + } + pos.dirAx = newAx; + }, + replaceIndent: function (scope, element, indent, attr) { + attr = attr || 'left'; + angular.element(element.children()[0]).css(attr, scope.$callbacks.calsIndent(indent)); + } + }; - scope.dragEnd = function (e) { - $params.dragEnd(e); - }; + return _$helper; + }] + ); - $params.bindDrag(); +angular.module('ntt.TreeDnD') + .factory('$TreeDnDPlugin', [ + '$injector', + function ($injector) { + var _fnget = function (name) { + if (angular.isDefined($injector) && $injector.has(name)) { + return $injector.get(name); + } + return null; + }; + return _fnget; + }] + ); - angular.element($window.document.body).bind('keydown', keydownHandler); - angular.element($window.document.body).bind('keyup', keyupHandler); - //unbind handler that retains scope - scope.$on( - '$destroy', function () { - angular.element($window.document.body).unbind('keydown', keydownHandler); - angular.element($window.document.body).unbind('keyup', keyupHandler); - if (scope.statusElm) { - scope.statusElm.remove(); +angular.module('ntt.TreeDnD') + .factory('$TreeDnDTemplate', [ + '$templateCache', + function ($templateCache) { + var templatePath = 'template/TreeDnD/TreeDnD.html', + copyPath = 'template/TreeDnD/TreeDnDStatusCopy.html', + movePath = 'template/TreeDnD/TreeDnDStatusMove.html', + scopes = {}, + temp, + _$init = { + setMove: function (path, scope) { + if (!scopes[scope.$id]) { + scopes[scope.$id] = {}; } - - if (scope.placeElm) { - scope.placeElm.remove(); + scopes[scope.$id].movePath = path; + }, + setCopy: function (path, scope) { + if (!scopes[scope.$id]) { + scopes[scope.$id] = {}; + } + scopes[scope.$id].copyPath = path; + }, + getPath: function () { + return templatePath; + }, + getCopy: function (scope) { + if (scopes[scope.$id] && scopes[scope.$id].copyPath) { + temp = $templateCache.get(scopes[scope.$id].copyPath); + if (temp) { + return temp; + } + } + return $templateCache.get(copyPath); + }, + getMove: function (scope) { + if (scopes[scope.$id] && scopes[scope.$id].movePath) { + temp = $templateCache.get(scopes[scope.$id].movePath); + if (temp) { + return temp; + } } + return $templateCache.get(movePath); } - ); - } + }; return _$init; - } - ]); + }] + ); angular.module('ntt.TreeDnD') - .factory('$TreeDnDControl', function () { - var _target, _parent, - i, len; + .factory('$TreeDnDViewport', fnInitTreeDnDViewport); - function fnSetCollapse(node) { - node.__expanded__ = false; - } +fnInitTreeDnDViewport.$inject = ['$window', '$document', '$timeout', '$q', '$compile']; - function fnSetExpand(node) { - node.__expanded__ = true; - } +function fnInitTreeDnDViewport($window, $document, $timeout, $q, $compile) { - function _$init(scope) { - var n, tree = { - selected_node: null, - for_all_descendants: scope.for_all_descendants, - select_node: function (node) { - if (!node) { - if (tree.selected_node) { - delete tree.selected_node.__selected__; - } - tree.selected_node = null; - return null; - } + var viewport = null, + isUpdating = false, + isRender = false, + updateAgain = false, + viewportRect, + items = [], + nodeTemplate, + updateTimeout, + renderTime, + windowListenersBound = false, + $initViewport = { + setViewport: setViewport, + getViewport: getViewport, + add: add, + remove: remove, + setTemplate: setTemplate, + getItems: getItems, + updateDelayed: updateDelayed, + destroy: destroy + }, + eWindow = angular.element($window); - if (node !== tree.selected_node) { - if (tree.selected_node) { - delete tree.selected_node.__selected__; - } - node.__selected__ = true; - tree.selected_node = node; - tree.expand_all_parents(node); - if (angular.isFunction(tree.on_select)) { - tree.on_select(node); - } - } + return $initViewport; - return node; - }, - deselect_node: function () { - _target = null; - if (tree.selected_node) { - delete tree.selected_node.__selected__; - _target = tree.selected_node; - tree.selected_node = null; - } - return _target; - }, - get_parent: function (node) { - node = node || tree.selected_node; + /** + * Bind window event listeners (lazily on first add) + */ + function bindWindowListeners() { + if (!windowListenersBound) { + eWindow.on('load resize scroll', updateDelayed); + windowListenersBound = true; + } + } - if (node && node.__parent_real__ !== null) { - return scope.tree_nodes[node.__parent_real__]; - } - return null; - }, - for_all_ancestors: function (node, fn) { - _parent = tree.get_parent(node); - if (_parent) { - if (fn(_parent)) { - return false; - } + /** + * Unbind window event listeners and clean up resources + */ + function destroy() { + if (windowListenersBound) { + eWindow.off('load resize scroll', updateDelayed); + windowListenersBound = false; + } - return tree.for_all_ancestors(_parent, fn); - } - return true; - }, - expand_all_parents: function (node) { - node = node || tree.selected_node; + // Cancel any pending timeouts + if (updateTimeout) { + $timeout.cancel(updateTimeout); + updateTimeout = null; + } + if (renderTime) { + $timeout.cancel(renderTime); + renderTime = null; + } - if (angular.isObject(node)) { - tree.for_all_ancestors(node, fnSetExpand); - } - }, - collapse_all_parents: function (node) { - node = node || tree.selected_node; - if (angular.isObject(node)) { - tree.for_all_ancestors(node, fnSetCollapse); - } - }, + // Clear all item references + items.length = 0; + viewport = null; + nodeTemplate = null; + isUpdating = false; + isRender = false; + updateAgain = false; + } - reload_data: function () { - return scope.reload_data(); - }, - add_node: function (parent, new_node, index) { - if (typeof index !== 'number') { - if (parent) { - parent.__children__.push(new_node); - parent.__expanded__ = true; - } else { - scope.treeData.push(new_node); - } - } else { - if (parent) { - parent.__children__.splice(index, 0, new_node); - parent.__expanded__ = true; - } else { - scope.treeData.splice(index, 0, new_node); - } - } - return new_node; - }, - add_node_root: function (new_node) { - tree.add_node(null, new_node); - return new_node; - }, - expand_all: function () { - len = scope.treeData.length; - for (i = 0; i < len; i++) { - tree.for_all_descendants(scope.treeData[i], fnSetExpand); - } - }, - collapse_all: function () { - len = scope.treeData.length; - for (i = 0; i < len; i++) { - tree.for_all_descendants(scope.treeData[i], fnSetCollapse); - } - }, - remove_node: function (node) { - node = node || tree.selected_node; + function update() { - if (angular.isObject(node)) { - if (node.__parent_real__ !== null) { - _parent = tree.get_parent(node).__children__; - } else { - _parent = scope.treeData; - } + viewportRect = { + width: eWindow.prop('offsetWidth') || document.documentElement.clientWidth, + height: eWindow.prop('offsetHeight') || document.documentElement.clientHeight, + top: $document[0].body.scrollTop || $document[0].documentElement.scrollTop, + left: $document[0].body.scrollLeft || $document[0].documentElement.scrollLeft + }; - _parent.splice(node.__index__, 1); + if (isUpdating || isRender) { + updateAgain = true; + return; + } + isUpdating = true; - tree.reload_data(); + recursivePromise(); + } - if (tree.selected_node === node) { - tree.selected_node = null; - } - } - }, - expand_node: function (node) { - node = node || tree.selected_node; + function recursivePromise() { + if (isRender) { + return; + } - if (angular.isObject(node)) { - node.__expanded__ = true; - return node; - } - }, - collapse_node: function (node) { - node = node || tree.selected_node; + var number = number > 0 ? number : items.length, item; - if (angular.isObject(node)) { - node.__expanded__ = false; - return node; - } - }, - get_selected_node: function () { - return tree.selected_node; - }, - get_first_node: function () { - len = scope.treeData.length; - if (len > 0) { - return scope.treeData[0]; - } + if (number > 0) { + item = items[0]; - return null; - }, - get_children: function (node) { - node = node || tree.selected_node; + isRender = true; + renderTime = $timeout(function () { + //item.element.html(nodeTemplate); + //$compile(item.element.contents())(item.scope); - return node.__children__; - }, - get_siblings: function (node) { - node = node || tree.selected_node; - if (angular.isObject(node)) { - _parent = tree.get_parent(node); - if (_parent) { - _target = _parent.__children__; - } else { - _target = scope.treeData; - } - return _target; - } - }, - get_next_sibling: function (node) { - node = node || tree.selected_node; - if (angular.isObject(node)) { - _target = tree.get_siblings(node); - n = _target.length; - if (node.__index__ < n) { - return _target[node.__index__ + 1]; - } - } - }, - get_prev_sibling: function (node) { - node = node || tree.selected_node; - _target = tree.get_siblings(node); - if (node.__index__ > 0) { - return _target[node.__index__ - 1]; - } - }, - get_first_child: function (node) { - node = node || tree.selected_node; - if (angular.isObject(node)) { - _target = node.__children__; - if (_target && _target.length > 0) { - return node.__children__[0]; - } - } - return null; - }, - get_closest_ancestor_next_sibling: function (node) { - node = node || tree.selected_node; - _target = tree.get_next_sibling(node); - if (_target) { - return _target; - } + items.splice(0, 1); + isRender = false; + number--; + $timeout.cancel(renderTime); + recursivePromise(); + }, 0); - _parent = tree.get_parent(node); - if (_parent) { - return tree.get_closest_ancestor_next_sibling(_parent); - } + } else { + isUpdating = false; + if (updateAgain) { + updateAgain = false; + update(); + } + } - return null; - }, - get_next_node: function (node) { - node = node || tree.selected_node; + } - if (angular.isObject(node)) { - _target = tree.get_first_child(node); - if (_target) { - return _target; - } else { - return tree.get_closest_ancestor_next_sibling(node); - } - } - }, - get_prev_node: function (node) { - node = node || tree.selected_node; + /** + * Check if a point is inside specified bounds + * @param x + * @param y + * @param bounds + * @returns {boolean} + */ + function pointIsInsideBounds(x, y, bounds) { + return x >= bounds.left && + y >= bounds.top && + x <= bounds.left + bounds.width && + y <= bounds.top + bounds.height; + } - if (angular.isObject(node)) { - _target = tree.get_prev_sibling(node); - if (_target) { - return tree.get_last_descendant(_target); - } + /** + * @name setViewport + * @desciption Set the viewport element + * @param element + */ + function setViewport(element) { + viewport = element; + } - _parent = tree.get_parent(node); - return _parent; - } - }, - get_last_descendant: scope.getLastDescendant, - select_parent_node: function (node) { - node = node || tree.selected_node; + /** + * Return the current viewport + * @returns {*} + */ + function getViewport() { + return viewport; + } - if (angular.isObject(node)) { - _parent = tree.get_parent(node); - if (_parent) { - return tree.select_node(_parent); - } - } - }, - select_first_node: function () { - var firstNode = tree.get_first_node(); - return tree.select_node(firstNode); - }, - select_next_sibling: function (node) { - node = node || tree.selected_node; + /** + * trigger an update + */ + function updateDelayed() { + $timeout.cancel(updateTimeout); + updateTimeout = $timeout(function () { + update(); + }, 0); + } - if (angular.isObject(node)) { - _target = tree.get_next_sibling(node); - if (_target) { - return tree.select_node(_target); - } - } - }, - select_prev_sibling: function (node) { - node = node || tree.selected_node; + /** + * Add listener for event + * @param element + * @param callback + */ + function add(scope, element) { + // Lazily bind window listeners on first add + bindWindowListeners(); + updateDelayed(); + items.push({ + element: element, + scope: scope + }); + } - if (angular.isObject(node)) { - _target = tree.get_prev_sibling(node); - if (_target) { - return tree.select_node(_target); - } - } - }, - select_next_node: function (node) { - node = node || tree.selected_node; + function remove(scope, element) { + var i = items.length; + while (i--) { + if (items[i].scope === scope || (element && items[i].element === element)) { + // Clear references before removing + items[i].scope = null; + items[i].element = null; + items.splice(i, 1); + } + } + // Auto-cleanup: unbind window listeners when no items remain + if (items.length === 0 && windowListenersBound) { + eWindow.off('load resize scroll', updateDelayed); + windowListenersBound = false; + } + } - if (angular.isObject(node)) { - _target = tree.get_next_node(node); - if (_target) { - return tree.select_node(_target); - } - } - }, - select_prev_node: function (node) { - node = node || tree.selected_node; + function setTemplate(scope, template) { + nodeTemplate = template; + } - if (angular.isObject(node)) { - _target = tree.get_prev_node(node); - if (_target) { - return tree.select_node(_target); - } - } - } - }; - angular.extend(scope.tree, tree); - return scope.tree; - } + /** + * Get list of items + * @returns {Array} + */ + function getItems() { + return items; + } +} - return _$init; - }); angular.module('template/TreeDnD/TreeDnD.html', []).run( ['$templateCache', function ($templateCache) { @@ -3349,8 +3634,8 @@ angular.module('template/TreeDnD/TreeDnD.html', []).run( ' ', ' ', ' ', - ' ', ' 0&&(e.__expanded__=!e.__expanded__)};var g=function(e){return"#"+e.__parent__+"#"+e[n.primary_key]};n.getHash=g,n.$callbacks={getHash:g,setHash:function(e){var n=g(e);return(angular.isUndefinedOrNull(e.__hashKey__)||e.__hashKey__!==n)&&(e.__hashKey__=n),e},for_all_descendants:n.for_all_descendants,accept:function(){return!0===n.dropEnabled},calsIndent:function(e,t,a){var l=0,r=a?0:n.indent_plus;return t||(l=n.indent_unit?n.indent_unit:"px"),e-1<1?r+l:n.indent*(e-1)+r+l},droppable:function(){return!0===n.dropEnabled},draggable:function(){return!0===n.dragEnabled},beforeDrop:function(){return!0},changeKey:function(e){var t=e.__uid__;e.__uid__=Math.random(),e.__selected__&&delete e.__selected__,"__uid__"!==n.primary_key&&(t=(t=""+e[n.primary_key]).replace(/_#.+$/g,"")+"_#"+e.__uid__,e[n.primary_key]=t)},clone:function(e){return u=angular.copy(e),this.for_all_descendants(u,this.changeKey),u},remove:function(e,t,a,l){var r=t.splice(e.__index__,1)[0];return l||n.reload_data(),r},clearInfo:function(e){delete e.__inited__,delete e.__visible__},add:function(e,n,t){this.for_all_descendants(e,this.clearInfo),t&&(t.length>-1&&n>-1?t.splice(n,0,e):t.push(e))}},n.deleteScope=function(e,t){var a=t.__hashKey__;n.$globals[a]&&n.$globals[a]===e&&delete n.$globals[a]},n.setScope=function(e,t){var a=t.__hashKey__;n.$globals[a]!==e&&(n.$globals[a]=e)},n.getScope=function(e){if(e){var t=e.__hashKey__;return n.$globals[t]}return n},(a.enableDrag||a.enableDrop)&&(n.placeElm=null,n.dragEnabled=null,n.dropEnabled=null,n.horizontal=null,a.enableDrag&&(n.dragDelay=0,n.enabledMove=!0,n.statusMove=!0,n.enabledHotkey=!1,n.enabledCollapse=null,n.statusElm=null,n.dragging=null,angular.extend(n.$callbacks,{beforeDrag:function(){return!0},dragStop:function(e,n){if(!e||!e.changed&&e.drag.enabledMove||!n)return null;e.target.reload_data(),e.target!==e.drag&&e.drag.enabledMove&&e.drag.reload_data()},dropped:function(e){if(!e)return null;var n=e.node,t=null,a=e.move,l=null,r=e.parent||e.drag.treeData,i=a.parent||e.target.treeData,d=e.drag.enabledMove;return!(!e.changed&&d)&&(!!e.target.$callbacks.accept(e,e.move,e.changed)&&(d?(l=r,angular.isDefined(l.__children__)&&(l=l.__children__),t=e.drag.$callbacks.remove(n,l,e.drag.$callbacks,!0)):t=e.drag.$callbacks.clone(n,e.drag.$callbacks),d&&e.drag===e.target&&r===i&&a.pos>=e.node.__index__&&a.pos--,(l=i).__children__&&(l=l.__children__),e.target.$callbacks.add(t,a.pos,l,e.drag.$callbacks),!0))},dragStart:function(e){},dragMove:function(e){}}),n.setDragging=function(e){n.dragging=e},n.enableMove=function(e){n.enabledMove="boolean"!=typeof e||e},a.enableStatus&&(n.enabledStatus=!1,n.hideStatus=function(){n.statusElm&&n.statusElm.addClass(n.$class.hidden)},n.refreshStatus=function(){if(n.dragging&&n.enabledStatus){var e=n.statusElm;n.enabledMove?n.statusElm=angular.element(d.getMove(n)):n.statusElm=angular.element(d.getCopy(n)),e!==n.statusElm&&(e&&(n.statusElm.attr("class",e.attr("class")),n.statusElm.attr("style",e.attr("style")),e.remove()),r.find("body").append(n.statusElm)),n.statusElm.removeClass(n.$class.hidden)}},n.setPositionStatus=function(e){n.statusElm&&(n.statusElm.css({left:e.pageX+10+"px",top:e.pageY+15+"px","z-index":9999}),n.statusElm.addClass(n.$class.status))})),n.targeting=!1,n.getPrevSibling=function(e){if(e&&e.__index__>0){var t=e.__index__-1;return angular.isDefined(e.__parent_real__)?n.tree_nodes[e.__parent_real__].__children__[t]:n.treeData[t]}return null},n.getNode=function(e){return angular.isUndefinedOrNull(e)?null:n.tree_nodes[e]},n.initPlace=function(e,t){if(!n.placeElm)if(n.isTable){n.placeElm=angular.element(l.document.createElement("tr"));var a=n.colDefinitions.length;for(n.placeElm.append(angular.element(l.document.createElement("td")).addClass(n.$class.empty).addClass("indented").addClass(n.$class.place));a-- >0;)n.placeElm.append(angular.element(l.document.createElement("td")).addClass(n.$class.empty).addClass(n.$class.place))}else n.placeElm=angular.element(l.document.createElement("li")).addClass(n.$class.empty).addClass(n.$class.place);return t&&n.placeElm.css("height",s.height(t)+"px"),e?e[0].parentNode.insertBefore(n.placeElm[0],e[0]):n.getElementChilds().append(n.placeElm),n.placeElm},n.hidePlace=function(){n.placeElm&&n.placeElm.addClass(n.$class.hidden)},n.showPlace=function(){n.placeElm&&n.placeElm.removeClass(n.$class.hidden)},n.getScopeTree=function(){return n});n.$safeApply=function(e){var n=this.$root.$$phase;"$apply"===n||"$digest"===n?e&&"function"==typeof e&&e():this.$apply(e)},n.hiddenChild=function(e,t){var a=n.getScope(e);return a?t&&t.__expanded__&&t.__visible__?(a.$element.removeClass(n.$class.hidden),e.__visible__=!0):(a.$element.addClass(n.$class.hidden),e.__visible__=!1):t&&t.__expanded__&&t.__visible__?e.__visible__=!0:e.__visible__=!1,!1===e.__expanded__};var f,p,h,m,b,$,v,y,D,x,E,T,w,C,S,k,O={showParent:!0,showChild:!1,beginAnd:!0},M=[["enableDrag",[["boolean","enableStatus",null,"enabledStatus"],["boolean","enableMove",null,"enabledMove"],["number","dragDelay",0,null,0],["boolean","enableCollapse",null,"enabledCollapse"],["boolean","enableHotkey",null,"enabledHotkey",null,function(e){n.enabledMove=!e&&n.statusMove}]]],[["enableDrag","enableStatus"],[["string","templateCopy",a.templateCopy,"templateCopy",null,function(e){e&&i.get(e)&&d.setCopy(e,n)}],["string","templateMove",a.templateMove,"templateMove",null,function(e){e&&i.get(e)&&d.setMove(e,n)}]]],[[["enableDrag","enableDrop"]],[["number","dragBorder",30,"dragBorder",30]]],["*",[["boolean","treeTable",!0,"treeTable",null],["boolean","horizontal"],["callback","treeClass",function(e){switch(typeof e){case"string":n.$tree_class=e;break;case"object":angular.extend(n.$class,e),n.$tree_class=n.$class.tree;break;default:n.$tree_class=a.treeClass}},"treeClass",function(){n.$tree_class=n.$class.tree+" table"},null,function(){if(/^(\s+[\w\-]+){2,}$/g.test(" "+a.treeClass))return n.$tree_class=a.treeClass.trim(),!0}],[["object","string"],"expandOn",j,"expandingProperty",j,function(e){angular.isUndefinedOrNull(e)&&(n.expandingProperty=a.expandOn)}],["object","treeControl",angular.isDefined(n.tree)?n.tree:{},"tree",null,function(e){angular.isFunction(h)||(h=_("$TreeDnDControl")),angular.isFunction(h)&&angular.extend(e,h(n))}],[["array","object"],"columnDefs",I,"colDefinitions",I,function(e){!angular.isUndefinedOrNull(e)&&angular.isArray(e)||(n.colDefinitions=I())}],[["object","string","array","function"],"orderBy",a.orderBy],[["object","array"],"filter",null,"filter",null,function(e){var t=!1;if(angular.isDefined(e)&&!angular.isArray(e)){var a,l=Object.keys(e),r=l.length;if(r>0)for(a=0;a-1?n[r]=t:angular.isFunction(l)?n[r]=l(t):n[r]=l,angular.isFunction(d)&&d(n[r],n)},!0):angular.isFunction(i)?n[r]=i():angular.isUndefined(i)||(n[r]=i)}}function j(){if(n.treeData&&n.treeData.length){var e,t,a=n.treeData[0],l=Object.keys(a),r=new RegExp("^__([a-zA-Z0-9_-]*)__$");for(t=0,e=l.length;t0&&(t.__expanded__=r0)for(o=0;o0){var i;for(i=0;i0&&(o=s,e.html(""));return function(e,s,u){if(u.enableDrag){var g=_("$TreeDnDDrag");angular.isFunction(g)&&g(e,s,l,r)}s.ready(function(){function l(e,t){var l,r=e[0].querySelector("[tree-dnd-node]");if(t.isTable=null,r){if(r=angular.element(r),l=r.attr("ng-include")){var d=a(l)(t)||l;if("string"==typeof d)return n.get(d,{cache:i}).then(function(e){var n=e.data||"";n=n.trim();var a=document.createElement("div");a.innerHTML=n,a=angular.element(a),t.isTable=!a[0].querySelector("[tree-dnd-nodes]")})}else t.isTable=!r[0].querySelector("[tree-dnd-nodes]");c.setTemplate(t,t.templateNode)}}var r;o.length>0?(r=l(angular.element(o.trim()),e),angular.isObject(r)?r.then(function(){s.append(t(o)(e))}):s.append(t(o)(e))):n.get(u.templateUrl||d.getPath(),{cache:i}).then(function(n){var a=n.data||"";a=angular.element(a.trim()),r=l(a,e),angular.isObject(r)?r.then(function(){s.append(t(a)(e))}):s.append(t(a)(e))})})}}}}function n(e,n,t,a,l){var r,i,d=null,o=!1,s=!1,_=!1,c=[],u={setViewport:function(e){d=e},getViewport:function(){return d},add:function(e,n){p(),c.push({element:n,scope:e})},setTemplate:function(e,n){n},getItems:function(){return c},updateDelayed:p},g=angular.element(e);return g.on("load resize scroll",p),u;function f(){({width:g.prop("offsetWidth")||document.documentElement.clientWidth,height:g.prop("offsetHeight")||document.documentElement.clientHeight,top:n[0].body.scrollTop||n[0].documentElement.scrollTop,left:n[0].body.scrollLeft||n[0].documentElement.scrollLeft}),o||s?_=!0:(o=!0,function e(){if(s)return;var n=n>0?n:c.length;n>0?(c[0],s=!0,i=t(function(){c.splice(0,1),s=!1,n--,t.cancel(i),e()},0)):(o=!1,_&&(_=!1,f()))}())}function p(){t.cancel(r),r=t(function(){f()},0)}}angular.isUndefinedOrNull=function(e){return angular.isUndefined(e)||null===e},angular.isDefined=function(e){return!(angular.isUndefined(e)||null===e)},angular.module("ntt.TreeDnD",["template/TreeDnD/TreeDnD.html"]).constant("$TreeDnDClass",{tree:"tree-dnd",empty:"tree-dnd-empty",hidden:"tree-dnd-hidden",node:"tree-dnd-node",nodes:"tree-dnd-nodes",handle:"tree-dnd-handle",place:"tree-dnd-placeholder",drag:"tree-dnd-drag",status:"tree-dnd-status",icon:{1:"glyphicon glyphicon-minus",0:"glyphicon glyphicon-plus","-1":"glyphicon glyphicon-file"}}),angular.module("ntt.TreeDnD").directive("compile",["$compile",function(e){return{restrict:"A",link:function(n,t,a){n.$watch(a.compile,function(a){a&&(angular.isFunction(t.empty)?t.empty():t.html(""),t.append(e(a)(n)))})}}}]).directive("compileReplace",["$compile",function(e){return{restrict:"A",link:function(n,t,a){n.$watch(a.compileReplace,function(a){a&&t.replaceWith(e(a)(n))})}}}]),angular.module("ntt.TreeDnD").directive("treeDndNodeHandle",function(){return{restrict:"A",scope:!0,link:function(e,n){e.$type="TreeDnDNodeHandle",e.$class.handle&&n.addClass(e.$class.handle)}}}),angular.module("ntt.TreeDnD").directive("treeDndNode",["$TreeDnDViewport",function(e){return{restrict:"A",replace:!0,link:function(n,t,a){n.$node_class="",n.$class.node&&(t.addClass(n.$class.node),n.$node_class=n.$class.node);var l,r="boolean"==typeof n.dragEnabled||"boolean"==typeof n.dropEnabled,i=a.treeDndNode,d=!0;e.add(n,t),r&&(n.$type="TreeDnDNode",n.getData=function(){return n[i]});n.$element=t,n[i].__inited__=!0,n.getElementChilds=function(){return angular.element(t[0].querySelector("[tree-dnd-nodes]"))},n.setScope(n,n[i]),n.getScopeNode=function(){return n};var o,s,_=[],c=Object.keys(n[i]),u=c.length,g=n[i].__hashKey__,f=["__visible__","__children__","__level__","__index__","__index_real__","__parent__","__parent_real__","__dept__","__icon__","__icon_class__"],p=["__expanded__"],h=p.length;for(s=0;s0&&(i=e(r[t],t,a),delete r[t],r.__children__=i),d.push(r);return d}}}),angular.module("ntt.TreeDnD").factory("$TreeDnDHelper",["$document","$window",function(e,n){return{nodrag:function(e){return void 0!==e.attr("data-nodrag")},eventObj:function(e){var n=e;return void 0!==e.targetTouches?n=e.targetTouches.item(0):void 0!==e.originalEvent&&void 0!==e.originalEvent.targetTouches&&(n=e.originalEvent.targetTouches.item(0)),n},dragInfo:function(e){var n=e.getData(),t=e.getScopeTree(),a=e.getNode(n.__parent_real__);return{node:n,parent:a,move:{parent:a,pos:n.__index__},scope:e,target:t,drag:t,drop:e.getPrevSibling(n),changed:!1}},height:function(e){return e.prop("scrollHeight")},width:function(e){return e.prop("scrollWidth")},offset:function(t){var a=t[0].getBoundingClientRect();return{width:t.prop("offsetWidth"),height:t.prop("offsetHeight"),top:a.top+(n.pageYOffset||e[0].body.scrollTop||e[0].documentElement.scrollTop),left:a.left+(n.pageXOffset||e[0].body.scrollLeft||e[0].documentElement.scrollLeft)}},positionStarted:function(e,n){return{offsetX:e.pageX-this.offset(n).left,offsetY:e.pageY-this.offset(n).top,startX:e.pageX,lastX:e.pageX,startY:e.pageY,lastY:e.pageY,nowX:0,nowY:0,distX:0,distY:0,dirAx:0,dirX:0,dirY:0,lastDirX:0,lastDirY:0,distAxX:0,distAxY:0}},positionMoved:function(e,n,t){n.lastX=n.nowX,n.lastY=n.nowY,n.nowX=e.pageX,n.nowY=e.pageY,n.distX=n.nowX-n.lastX,n.distY=n.nowY-n.lastY,n.lastDirX=n.dirX,n.lastDirY=n.dirY,n.dirX=0===n.distX?0:n.distX>0?1:-1,n.dirY=0===n.distY?0:n.distY>0?1:-1;var a=Math.abs(n.distX)>Math.abs(n.distY)?1:0;if(t)return n.dirAx=a,void(n.moving=!0);n.dirAx!==a?(n.distAxX=0,n.distAxY=0):(n.distAxX+=Math.abs(n.distX),0!==n.dirX&&n.dirX!==n.lastDirX&&(n.distAxX=0),n.distAxY+=Math.abs(n.distY),0!==n.dirY&&n.dirY!==n.lastDirY&&(n.distAxY=0)),n.dirAx=a},replaceIndent:function(e,n,t,a){a=a||"left",angular.element(n.children()[0]).css(a,e.$callbacks.calsIndent(t))}}}]),angular.module("ntt.TreeDnD").factory("$TreeDnDPlugin",["$injector",function(e){return function(n){return angular.isDefined(e)&&e.has(n)?e.get(n):null}}]),angular.module("ntt.TreeDnD").factory("$TreeDnDTemplate",["$templateCache",function(e){var n,t={};return{setMove:function(e,n){t[n.$id]||(t[n.$id]={}),t[n.$id].movePath=e},setCopy:function(e,n){t[n.$id]||(t[n.$id]={}),t[n.$id].copyPath=e},getPath:function(){return"template/TreeDnD/TreeDnD.html"},getCopy:function(a){return t[a.$id]&&t[a.$id].copyPath&&(n=e.get(t[a.$id].copyPath))?n:e.get("template/TreeDnD/TreeDnDStatusCopy.html")},getMove:function(a){return t[a.$id]&&t[a.$id].movePath&&(n=e.get(t[a.$id].movePath))?n:e.get("template/TreeDnD/TreeDnDStatusMove.html")}}}]),angular.module("ntt.TreeDnD").factory("$TreeDnDViewport",n),n.$inject=["$window","$document","$timeout","$q","$compile"],angular.module("ntt.TreeDnD").factory("$TreeDnDFilter",["$filter",function(e){return function(e,t,a,d){if(!angular.isArray(e)||0===e.length)return e;var o,s,_;if(_=function e(n){var t,a,l,r,i;if(angular.isObject(n)&&!angular.isArray(n)){if(l=Object.keys(n),a=l.length,r=[],a>0)for(t=0;t-1:null}}function a(e,n,l){if(angular.isArray(n))return function(e,n,t){var l,r=n.length||0,i=!1;if(0===r)return null;for(l=0;lt.document_height&&(i=t.document_height-10),r+10>t.document_width&&(r=t.document_width-10),t.dragElm.css({left:r+a.$callbacks.calsIndent(t.offsetEdge+1,!0,!0)+"px",top:i+"px"}),a.enabledStatus&&a.setPositionStatus(e);var d=window.pageYOffset||t.$window.document.documentElement.scrollTop,o=d+(window.innerHeight||t.$window.document.clientHeight||t.$window.document.clientHeight);if(ol.pageY&&window.scrollBy(0,-10),n.positionMoved(e,t.pos,t.firstMoving),t.firstMoving)return void(t.firstMoving=!1);var s,_,c,u,g,f,p,h,m,b,$=l.pageX-t.$window.document.body.scrollLeft,v=l.pageY-(window.pageYOffset||t.$window.document.documentElement.scrollTop),y=!0,D=!0,x=t.dragInfo,E=x.move,T=x.node,w=x.drop,C=x.target,S=function(e,t){if(t.placeElm){var a=n.offset(t.placeElm);if(a.top<=e.pageY&&e.pageY<=a.top+a.height&&a.left<=e.pageX&&e.pageX<=a.left+a.width)return!0}return!1}(e,t);if(!S){if(s=angular.element(t.$window.document.elementFromPoint($,v)),!(_=s.scope())||!_.$callbacks||!_.$callbacks.droppable())return;if(b=function(){return C=_.getScopeTree(),h=x.target,x.target!==C&&(h.hidePlace(),h.targeting=!1,C.targeting=!0,x.target=C,t.placeElm=C.initPlace(_.$element,t.dragElm),h=null,f=!0),!0},angular.isFunction(_.getScopeNode)){if(_=_.getScopeNode(),!b())return}else{if("TreeDnDNodes"!==_.$type&&"TreeDnD"!==_.$type)return;if(!_.tree_nodes)return;if(0===_.tree_nodes.length){if(!b())return;g=!0}}}if((t.pos.dirAx&&!f||S)&&(D=!1,_=x.scope),!_.$element&&!_)return;if(g)E.parent=null,E.pos=0,w=null;else if(D){if(s=_.$element,angular.isUndefinedOrNull(s))return;if(u=n.offset(s),_.horizontal&&!_.isTable)c=l.pageXu.top+k)return;c=l.pageY=C.dragBorder))return;if(t.pos.distAxX=0,t.pos.distX>0){if(!(m=w)){if(!(E.pos-1>=0))return;m=E.parent.__children__[E.pos-1]}if(x.drag===x.target&&m===T&&a.enabledMove&&(m=C.getPrevSibling(m)),!m||!m.__visible__)return;var M=m.__children__.length;E.parent=m,E.pos=M,w=M>0?m.__children__[M-1]:null}else{if(!(t.pos.distX<0))return;if(!(h=E.parent)||!(0===h.__children__.length||h.__children__.length-10?i.treeData[0]:null},get_children:function(e){return(e=e||o.selected_node).__children__},get_siblings:function(t){if(t=t||o.selected_node,angular.isObject(t))return n=o.get_parent(t),e=n?n.__children__:i.treeData},get_next_sibling:function(n){if(n=n||o.selected_node,angular.isObject(n)&&(e=o.get_siblings(n),d=e.length,n.__index__0)return e[n.__index__-1]},get_first_child:function(n){return n=n||o.selected_node,angular.isObject(n)&&(e=n.__children__)&&e.length>0?n.__children__[0]:null},get_closest_ancestor_next_sibling:function(t){return t=t||o.selected_node,(e=o.get_next_sibling(t))?e:(n=o.get_parent(t))?o.get_closest_ancestor_next_sibling(n):null},get_next_node:function(n){if(n=n||o.selected_node,angular.isObject(n))return(e=o.get_first_child(n))||o.get_closest_ancestor_next_sibling(n)},get_prev_node:function(t){if(t=t||o.selected_node,angular.isObject(t))return(e=o.get_prev_sibling(t))?o.get_last_descendant(e):n=o.get_parent(t)},get_last_descendant:i.getLastDescendant,select_parent_node:function(e){if(e=e||o.selected_node,angular.isObject(e)&&(n=o.get_parent(e)))return o.select_node(n)},select_first_node:function(){var e=o.get_first_node();return o.select_node(e)},select_next_sibling:function(n){if(n=n||o.selected_node,angular.isObject(n)&&(e=o.get_next_sibling(n)))return o.select_node(e)},select_prev_sibling:function(n){if(n=n||o.selected_node,angular.isObject(n)&&(e=o.get_prev_sibling(n)))return o.select_node(e)},select_next_node:function(n){if(n=n||o.selected_node,angular.isObject(n)&&(e=o.get_next_node(n)))return o.select_node(e)},select_prev_node:function(n){if(n=n||o.selected_node,angular.isObject(n)&&(e=o.get_prev_node(n)))return o.select_node(e)}};return angular.extend(i.tree,o),i.tree}}),angular.module("template/TreeDnD/TreeDnD.html",[]).run(["$templateCache",function(e){e.put("template/TreeDnD/TreeDnD.html",[''," "," ",' ",' "," "," "," ",' "," ",' "," "," ","
'," {{expandingProperty.displayName || expandingProperty.field || expandingProperty}}"," '," {{col.displayName || col.field}}","
'," ",' '," "," {{node[expandingProperty.field] || node[expandingProperty]}}"," '," {{node[col.field]}}","
"].join("\n")),e.put("template/TreeDnD/TreeDnDStatusCopy.html",''),e.put("template/TreeDnD/TreeDnDStatusMove.html",'')}])}(); +(()=>{function e(P,d,o,_,Y,j,X,I,N,F,H,U,s){return{restrict:"E",scope:!0,replace:!0,controller:["$scope","$element","$attrs",function(u,e,d){function t(e){return"#"+e.__parent__+"#"+e[u.primary_key]}var l,n;u.indent=20,u.indent_plus=15,u.indent_unit="px",u.$tree_class="table",u.primary_key="__uid__",u.$type="TreeDnD",u.colDefinitions=[],u.$globals={},u.$class={},u.treeData=[],u.tree_nodes=[],u.$class=angular.copy(F),angular.extend(u.$class.icon,{1:d.iconExpand||"glyphicon glyphicon-minus",0:d.iconCollapse||"glyphicon glyphicon-plus","-1":d.iconLeaf||"glyphicon glyphicon-file"}),u.for_all_descendants=function(e,n,t,l){if(angular.isFunction(n)){var a,r,i;if(n(e,t))return!1;for(r=(i=e.__children__)?i.length:0,a=0;a=e.node.__index__&&l.pos--,(a=i).__children__&&(a=a.__children__),e.target.$callbacks.add(t,l.pos,a,e.drag.$callbacks),0))):null},dragStart:function(e){},dragMove:function(e){}}),u.setDragging=function(e){u.dragging=e},u.enableMove=function(e){u.enabledMove="boolean"!=typeof e||e},d.enableStatus)&&(u.enabledStatus=!1,u.hideStatus=function(){u.statusElm&&u.statusElm.addClass(u.$class.hidden)},u.refreshStatus=function(){var e;u.dragging&&u.enabledStatus&&(e=u.statusElm,u.statusElm=u.enabledMove?angular.element(N.getMove(u)):angular.element(N.getCopy(u)),e!==u.statusElm&&(e&&(u.statusElm.attr("class",e.attr("class")),u.statusElm.attr("style",e.attr("style")),e.remove()),j.find("body").append(u.statusElm)),u.statusElm.removeClass(u.$class.hidden))},u.setPositionStatus=function(e){u.statusElm&&(u.statusElm.css({left:e.pageX+10+"px",top:e.pageY+15+"px","z-index":9999}),u.statusElm.addClass(u.$class.status))}),u.targeting=!1,u.getPrevSibling=function(e){var n;return e&&0{l=l||e,"string"!=typeof n&&!angular.isArray(n)||angular.isFunction(i)&&i()||("string"==typeof d[e]?(i=u.$watch(d[e],function(e){"string"==typeof n&&typeof e===n||angular.isArray(n)&&-1l.pageY&&window.scrollBy(0,-10),v.positionMoved(e,n.pos,n.firstMoving),n.firstMoving)n.firstMoving=!1;else{var i,d,o,_,s,r=l.pageX-n.$window.document.body.scrollLeft,a=l.pageY-(window.pageYOffset||n.$window.document.documentElement.scrollTop),c=!0,u=!0,g=n.dragInfo,f=g.move,p=g.node,h=g.drop,m=g.target,e=((e,n)=>{if(n.placeElm){n=v.offset(n.placeElm);if(n.top<=e.pageY&&e.pageY<=n.top+n.height&&n.left<=e.pageX&&e.pageX<=n.left+n.width)return!0}return!1})(e,n);if(!e){if(i=angular.element(n.$window.document.elementFromPoint(r,a)),!(d=i.scope())||!d.$callbacks||!d.$callbacks.droppable())return;if(r=function(){return m=d.getScopeTree(),b=g.target,g.target!==m&&(b.hidePlace(),b.targeting=!1,m.targeting=!0,g.target=m,n.placeElm=m.initPlace(d.$element,n.dragElm),_=!(b=null)),!0},angular.isFunction(d.getScopeNode))d=d.getScopeNode(),r();else{if("TreeDnDNodes"!==d.$type&&"TreeDnD"!==d.$type)return;if(!d.tree_nodes)return;0===d.tree_nodes.length&&(r(),y=!0)}}if((n.pos.dirAx&&!_||e)&&(u=!1,d=g.scope),d.$element||d){if(y)f.parent=null,f.pos=0,h=null;else if(u){if(i=d.$element,angular.isUndefinedOrNull(i))return;if(a=v.offset(i),d.horizontal&&!d.isTable)o=l.pageXa.top+r)return;o=l.pageY=m.dragBorder))return;if((n.pos.distAxX=0)Math.abs(n.distY)?1:0;t?(n.dirAx=e,n.moving=!0):(n.dirAx!==e?(n.distAxX=0,n.distAxY=0):(n.distAxX+=Math.abs(n.distX),0!==n.dirX&&n.dirX!==n.lastDirX&&(n.distAxX=0),n.distAxY+=Math.abs(n.distY),0!==n.dirY&&n.dirY!==n.lastDirY&&(n.distAxY=0)),n.dirAx=e)},replaceIndent:function(e,n,t,l){l=l||"left",angular.element(n.children()[0]).css(l,e.$callbacks.calsIndent(t))}}}]),angular.module("ntt.TreeDnD").factory("$TreeDnDPlugin",["$injector",function(n){return function(e){return angular.isDefined(n)&&n.has(e)?n.get(e):null}}]),angular.module("ntt.TreeDnD").factory("$TreeDnDTemplate",["$templateCache",function(n){var t,l={};return{setMove:function(e,n){l[n.$id]||(l[n.$id]={}),l[n.$id].movePath=e},setCopy:function(e,n){l[n.$id]||(l[n.$id]={}),l[n.$id].copyPath=e},getPath:function(){return"template/TreeDnD/TreeDnD.html"},getCopy:function(e){return l[e.$id]&&l[e.$id].copyPath&&(t=n.get(l[e.$id].copyPath))?t:n.get("template/TreeDnD/TreeDnDStatusCopy.html")},getMove:function(e){return l[e.$id]&&l[e.$id].movePath&&(t=n.get(l[e.$id].movePath))?t:n.get("template/TreeDnD/TreeDnDStatusMove.html")}}}]),angular.module("ntt.TreeDnD").factory("$TreeDnDViewport",n),n.$inject=["$window","$document","$timeout","$q","$compile"],angular.module("template/TreeDnD/TreeDnD.html",[]).run(["$templateCache",function(e){e.put("template/TreeDnD/TreeDnD.html",[''," "," ",' ",' "," "," "," ",' "," ",' "," "," ","
'," {{expandingProperty.displayName || expandingProperty.field || expandingProperty}}"," '," {{col.displayName || col.field}}","
'," ",' '," "," {{node[expandingProperty.field] || node[expandingProperty]}}"," '," {{node[col.field]}}","
"].join("\n")),e.put("template/TreeDnD/TreeDnDStatusCopy.html",''),e.put("template/TreeDnD/TreeDnDStatusMove.html",'')}])})(); //# sourceMappingURL=ng-tree-dnd.min.js.map diff --git a/dist/ng-tree-dnd.min.js.map b/dist/ng-tree-dnd.min.js.map index 349755e..ceeb3b6 100644 --- a/dist/ng-tree-dnd.min.js.map +++ b/dist/ng-tree-dnd.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["ng-tree-dnd.js"],"names":["fnInitTreeDnD","$timeout","$http","$compile","$parse","$window","$document","$templateCache","$TreeDnDTemplate","$TreeDnDClass","$TreeDnDHelper","$TreeDnDPlugin","$TreeDnDViewport","restrict","scope","replace","controller","$scope","$element","$attrs","passedExpand","_clone","indent","indent_plus","indent_unit","$tree_class","primary_key","$type","colDefinitions","$globals","$class","treeData","tree_nodes","angular","copy","extend","icon","1","iconExpand","0","iconCollapse","-1","iconLeaf","for_all_descendants","node","fn","parent","checkSibling","isFunction","_i","_len","_nodes","__children__","length","getLastDescendant","last_child","n","tree","selected_node","getElementChilds","element","querySelector","onClick","isDefined","on_click","setTimeout","onSelect","select_node","on_select","toggleExpand","fnCallback","$callbacks","expand","__expanded__","_fnGetHash","__parent__","getHash","setHash","_hashKey","isUndefinedOrNull","__hashKey__","accept","dropEnabled","calsIndent","level","skipUnit","skipEdge","unit","edge","droppable","draggable","dragEnabled","beforeDrop","changeKey","_key","__uid__","Math","random","__selected__","clone","this","remove","_this","delayReload","temp","splice","__index__","reload_data","clearInfo","__inited__","__visible__","add","pos","push","deleteScope","_hash","setScope","getScope","enableDrag","enableDrop","placeElm","horizontal","dragDelay","enabledMove","statusMove","enabledHotkey","enabledCollapse","statusElm","dragging","beforeDrag","dragStop","info","passed","changed","drag","target","dropped","_node","_nodeAdd","_move","move","_parent","_parentRemove","_parentAdd","isMove","dragStart","event","dragMove","setDragging","dragInfo","enableMove","val","enableStatus","enabledStatus","hideStatus","addClass","hidden","refreshStatus","statusElmOld","getMove","getCopy","attr","find","append","removeClass","setPositionStatus","e","css","left","pageX","top","pageY","z-index","status","targeting","getPrevSibling","_index","__parent_real__","getNode","index","initPlace","dragElm","isTable","document","createElement","_len_down","empty","place","height","parentNode","insertBefore","hidePlace","showPlace","getScopeTree","$safeApply","phase","$root","$$phase","$apply","hiddenChild","nodeScope","_fnInitFilter","_fnInitOrderBy","_fnGetControl","w","i","len","_curW","_typeW","_nameW","_defaultW","_scopeW","_NotW","_AfterW","_BeforeW","timeReloadData","tmpTreeData","_defaultFilterOption","showParent","showChild","beginAnd","_watches","isHotkey","templateCopy","_url","get","setCopy","templateMove","setMove","treeClass","test","trim","getExpandOn","expandOn","expandingProperty","$tree","getColDefs","colDefs","isArray","orderBy","filters","_passed","_iF","_keysF","Object","keys","_lenF","enabledFilter","option","isObject","filterOptions","primaryKey","indentUnit","optCallbacks","forEach","value","key","lenW","check_exist_attr","generateWatch","$watch","equals","timeLoadData","attrs","existAttr","isAnd","isUndefined","exist","for_each_attrs","type","nameAttr","valDefault","nameScope","fnNotExist","fnAfter","fnBefore","indexOf","_firstNode","_keys","_regex","RegExp","_col_defs","field","do_f","root","parent_real","visible","_icon","_index_real","_dept","expandLevel","__index_real__","__level__","__icon__","__icon_class__","__dept__","init_data","data","oData","_data","_tree_nodes","columnDefs","filter","updateLimit","$TreeLimit","compile","tElement","$_Template","_element","html","_fnInitDrag","ready","checkTreeTable","template","attrInclude","elemNode","treeInclude","cache","then","response","tempDiv","innerHTML","setTemplate","templateNode","promiseCheck","templateUrl","getPath","fnInitTreeDnDViewport","$q","updateTimeout","renderTime","viewport","isUpdating","isRender","updateAgain","items","$initViewport","setViewport","getViewport","updateDelayed","getItems","eWindow","on","update","width","prop","documentElement","clientWidth","clientHeight","body","scrollTop","scrollLeft","recursivePromise","number","cancel","module","constant","nodes","handle","directive","link","new_val","compileReplace","replaceWith","$node_class","childsElem","enabledDnD","keyNode","treeDndNode","first","getData","getScopeNode","objexpr","objprops","keyO","lenO","hashKey","skipAttr","keepAttr","lenKeep","join","newVal","oldVal","nodeOf","parentReal","_childs","$on","$nodes_class","$inject","factory","line2tree","parentKey","callback","parentId","rootIds","item","_primary","treeObjs","tree2tree","access_child","containKey","_copy","_child","_tree","nodrag","targetElm","eventObj","obj","undefined","targetTouches","originalEvent","drop","offset","boundingClientRect","getBoundingClientRect","pageYOffset","pageXOffset","positionStarted","offsetX","offsetY","startX","lastX","startY","lastY","nowX","nowY","distX","distY","dirAx","dirX","dirY","lastDirX","lastDirY","distAxX","distAxY","positionMoved","firstMoving","newAx","abs","moving","replaceIndent","children","$injector","name","has","scopes","path","$id","movePath","copyPath","$filter","options","keyChild","_filter","_fnConvert","_state","_fnBeforeClear","_fnAfter","filter_index","_fnBefore","fieldChild","parentPassed","_nodePassed","_childPassed","_filter_index","_fnCheck","err","_fnProccess","condition","conditions","for_each_filter","_iO","_keysO","_lenO","_callback","isNodePassed","isChildPassed","isParentPassed","__filtered__","__filtered_visible__","__filtered_index__","_fnOrderBy","fnOrderBy","_fnOrder","list","isString","_fnDragMove","$params","_$scope","dragStarted","preventDefault","getSelection","removeAllRanges","selection","leftElmPos","topElmPos","document_height","document_width","offsetEdge","top_scroll","window","bottom_scroll","innerHeight","scrollBy","targetScope","targetBefore","targetOffset","isEmpty","isSwapped","_scope","_target","fnSwapTree","targetX","targetY","isChanged","isVeritcal","_info","_drag","_drop","treeScope","isHolder","_offset","_fnPlaceHolder","elementFromPoint","_height","_prev","dragBorder","after","prepend","dragDelaying","_fnDragEnd","$$apply","_status","clearData","_fnBindDrag","unbind","dragEndEvent","dragMoveEvent","dragCancelEvent","_fnDragStartEvent","hasTouch","button","which","uiTreeDragging","eventElm","eventScope","dragScope","eventElmTagName","toLowerCase","_tbody","_frag","_width","_needCollapse","_copied","createDocumentFragment","bind","max","scrollHeight","offsetHeight","scrollWidth","offsetWidth","_fnDragStart","dragTimer","bindDrag","dragEnd","keydownHandler","keyCode","shiftKey","_fnKeydownHandler","keyupHandler","_fnKeyupHandler","fnSetCollapse","fnSetExpand","expand_all_parents","deselect_node","get_parent","for_all_ancestors","collapse_all_parents","add_node","new_node","add_node_root","expand_all","collapse_all","remove_node","expand_node","collapse_node","get_selected_node","get_first_node","get_children","get_siblings","get_next_sibling","get_prev_sibling","get_first_child","get_closest_ancestor_next_sibling","get_next_node","get_prev_node","get_last_descendant","select_parent_node","select_first_node","firstNode","select_next_sibling","select_prev_sibling","select_next_node","select_prev_node","run","put"],"mappings":"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","file":"ng-tree-dnd.min.js","sourcesContent":["/**\n * The MIT License (MIT)\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n * @preserve\n */\n\n/**\n * Implementing TreeDnD & Event DrapnDrop (allow drag multi tree-table include all type: table, ol, ul)\n * Demo: http://thienhung1989.github.io/angular-tree-dnd\n * Github: https://github.com/thienhung1989/angular-tree-dnd\n * @version 3.0.7\n * @preserve\n * (c) 2015 Nguyuễn Thiện Hùng - \n */\n(function () {\n 'use strict';\n angular.isUndefinedOrNull = isUndefinedOrNull;\n\n angular.isDefined = isDefined;\n\n angular.module('ntt.TreeDnD', ['template/TreeDnD/TreeDnD.html'])\n .constant('$TreeDnDClass', {\n tree: 'tree-dnd',\n empty: 'tree-dnd-empty',\n hidden: 'tree-dnd-hidden',\n node: 'tree-dnd-node',\n nodes: 'tree-dnd-nodes',\n handle: 'tree-dnd-handle',\n place: 'tree-dnd-placeholder',\n drag: 'tree-dnd-drag',\n status: 'tree-dnd-status',\n icon: {\n '1': 'glyphicon glyphicon-minus',\n '0': 'glyphicon glyphicon-plus',\n '-1': 'glyphicon glyphicon-file'\n }\n });angular.module('ntt.TreeDnD')\n .directive('compile', [\n '$compile',\n function ($compile) {\n return {\n restrict: 'A',\n link: function (scope, element, attrs) {\n scope.$watch(\n attrs.compile, function (new_val) {\n if (new_val) {\n if (angular.isFunction(element.empty)) {\n element.empty();\n } else {\n element.html('');\n }\n\n element.append($compile(new_val)(scope));\n }\n }\n );\n }\n };\n }]\n )\n .directive('compileReplace', [\n '$compile',\n function ($compile) {\n return {\n restrict: 'A',\n link: function (scope, element, attrs) {\n scope.$watch(\n attrs.compileReplace, function (new_val) {\n if (new_val) {\n element.replaceWith($compile(new_val)(scope));\n }\n }\n );\n }\n };\n }]\n );\n\nangular.module('ntt.TreeDnD')\r .directive('treeDndNodeHandle', function () {\r return {\r restrict: 'A',\r scope: true,\r link: function (scope, element/*, attrs*/) {\r scope.$type = 'TreeDnDNodeHandle';\r if (scope.$class.handle) {\r element.addClass(scope.$class.handle);\r }\r }\r };\r });\n\nangular.module('ntt.TreeDnD')\n .directive('treeDndNode', [\n '$TreeDnDViewport',\n function ($TreeDnDViewport) {\n return {\n restrict: 'A',\n replace: true,\n link: fnLink\n };\n\n function fnLink(scope, element, attrs) {\n\n scope.$node_class = '';\n\n if (scope.$class.node) {\n element.addClass(scope.$class.node);\n scope.$node_class = scope.$class.node;\n }\n var enabledDnD = typeof scope.dragEnabled === 'boolean' || typeof scope.dropEnabled === 'boolean',\n keyNode = attrs.treeDndNode,\n first = true,\n childsElem;\n $TreeDnDViewport.add(scope, element);\n\n if (enabledDnD) {\n scope.$type = 'TreeDnDNode';\n\n scope.getData = function () {\n return scope[keyNode];\n };\n }\n\n scope.$element = element;\n scope[keyNode].__inited__ = true;\n\n scope.getElementChilds = function () {\n return angular.element(element[0].querySelector('[tree-dnd-nodes]'));\n };\n\n scope.setScope(scope, scope[keyNode]);\n\n scope.getScopeNode = function () {\n return scope;\n };\n\n var objprops = [],\n objexpr,\n i, keyO = Object.keys(scope[keyNode]),\n lenO = keyO.length,\n hashKey = scope[keyNode].__hashKey__,\n skipAttr = [\n '__visible__',\n '__children__',\n '__level__',\n '__index__',\n '__index_real__',\n\n '__parent__',\n '__parent_real__',\n '__dept__',\n '__icon__',\n '__icon_class__'\n ],\n keepAttr = [\n '__expanded__'\n ],\n lenKeep = keepAttr.length;\n\n // skip __visible__\n for (i = 0; i < lenO + lenKeep; i++) {\n if (i < lenO) {\n if (skipAttr.indexOf(keyO[i]) === -1) {\n objprops.push(keyNode + '.' + keyO[i]);\n }\n } else {\n if (keyO.indexOf(keepAttr[i - lenO]) === -1) {\n objprops.push(keyNode + '.' + keepAttr[i - lenO]);\n }\n }\n }\n\n objexpr = '[' + objprops.join(',') + ']';\n\n scope.$watch(objexpr, fnWatchNode, true);\n\n scope.$on('$destroy', function () {\n scope.deleteScope(scope, scope[keyNode]);\n });\n\n function fnWatchNode(newVal, oldVal, scope) {\n\n var nodeOf = scope[keyNode],\n _icon;\n\n if (first) {\n _icon = nodeOf.__icon__;\n nodeOf.__icon_class__ = scope.$class.icon[_icon];\n } else {\n\n var parentReal = nodeOf.__parent_real__,\n parentNode = scope.tree_nodes[parentReal] || null,\n _childs = nodeOf.__children__,\n _len = _childs.length,\n _i;\n\n if (!nodeOf.__inited__) {\n nodeOf.__inited__ = true;\n }\n\n if (nodeOf.__hashKey__ !== hashKey) {\n // clear scope in $globals\n scope.deleteScope(scope, nodeOf);\n\n // add new scope into $globals\n scope.setScope(scope, nodeOf);\n hashKey = nodeOf.__hashKey__;\n }\n\n if (parentNode && (!parentNode.__expanded__ || !parentNode.__visible__)) {\n element.addClass(scope.$class.hidden);\n nodeOf.__visible__ = false;\n } else {\n element.removeClass(scope.$class.hidden);\n nodeOf.__visible__ = true;\n }\n\n if (_len === 0) {\n _icon = -1;\n } else {\n if (nodeOf.__expanded__) {\n _icon = 1;\n } else {\n _icon = 0;\n }\n }\n\n nodeOf.__icon__ = _icon;\n nodeOf.__icon_class__ = scope.$class.icon[_icon];\n\n if (scope.isTable) {\n for (_i = 0; _i < _len; _i++) {\n scope.for_all_descendants(_childs[_i], scope.hiddenChild, nodeOf, true);\n }\n } else {\n if (!childsElem) {\n childsElem = scope.getElementChilds();\n }\n\n if (nodeOf.__expanded__) {\n childsElem.removeClass(scope.$class.hidden);\n } else {\n childsElem.addClass(scope.$class.hidden);\n }\n }\n\n }\n\n first = false;\n\n }\n }\n }]\n );\n\nangular.module('ntt.TreeDnD')\n .directive('treeDndNodes', function () {\n return {\n restrict: 'A',\n replace: true,\n link: function (scope, element/*, attrs*/) {\n scope.$type = 'TreeDnDNodes';\n\n if (scope.$class.nodes) {\n element.addClass(scope.$class.nodes);\n scope.$nodes_class = scope.$class.nodes;\n } else {\n scope.$nodes_class = '';\n }\n }\n };\n });\n\nangular.module('ntt.TreeDnD')\n .directive(\n 'treeDnd', fnInitTreeDnD);\n\nfnInitTreeDnD.$inject = [\n '$timeout', '$http', '$compile', '$parse', '$window', '$document', '$templateCache',\n '$TreeDnDTemplate', '$TreeDnDClass', '$TreeDnDHelper', '$TreeDnDPlugin', '$TreeDnDViewport'\n];\n\nfunction fnInitTreeDnD($timeout, $http, $compile, $parse, $window, $document, $templateCache,\n $TreeDnDTemplate, $TreeDnDClass, $TreeDnDHelper, $TreeDnDPlugin, $TreeDnDViewport) {\n return {\n restrict: 'E',\n scope: true,\n replace: true,\n controller: ['$scope', '$element', '$attrs', fnController],\n compile: fnCompile\n };\n\n function fnController($scope, $element, $attrs) {\n $scope.indent = 20;\n $scope.indent_plus = 15;\n $scope.indent_unit = 'px';\n $scope.$tree_class = 'table';\n $scope.primary_key = '__uid__';\n\n $scope.$type = 'TreeDnD';\n // $scope.enabledFilter = null;\n $scope.colDefinitions = [];\n $scope.$globals = {};\n $scope.$class = {};\n\n $scope.treeData = [];\n $scope.tree_nodes = [];\n\n $scope.$class = angular.copy($TreeDnDClass);\n angular.extend(\n $scope.$class.icon, {\n '1': $attrs.iconExpand || 'glyphicon glyphicon-minus',\n '0': $attrs.iconCollapse || 'glyphicon glyphicon-plus',\n '-1': $attrs.iconLeaf || 'glyphicon glyphicon-file'\n }\n );\n\n $scope.for_all_descendants = function (node, fn, parent, checkSibling) {\n if (angular.isFunction(fn)) {\n var _i, _len, _nodes;\n\n if (fn(node, parent)) {\n // have error or need ignore children\n return false;\n }\n _nodes = node.__children__;\n _len = _nodes ? _nodes.length : 0;\n for (_i = 0; _i < _len; _i++) {\n if (!$scope.for_all_descendants(_nodes[_i], fn, node) && !checkSibling) {\n // skip sibling of node checking\n return false;\n }\n }\n }\n // succeed then continue\n return true;\n };\n\n $scope.getLastDescendant = function (node) {\n var last_child, n;\n if (!node) {\n node = $scope.tree ? $scope.tree.selected_node : false;\n }\n if (node === false) {\n return false;\n }\n n = node.__children__.length;\n if (n === 0) {\n return node;\n } else {\n last_child = node.__children__[n - 1];\n return $scope.getLastDescendant(last_child);\n }\n };\n\n $scope.getElementChilds = function () {\n return angular.element($element[0].querySelector('[tree-dnd-nodes]'));\n };\n\n $scope.onClick = function (node) {\n if (angular.isDefined($scope.tree) && angular.isFunction($scope.tree.on_click)) {\n // We want to detach from Angular's digest cycle so we can\n // independently measure the time for one cycle.\n setTimeout(\n function () {\n $scope.tree.on_click(node);\n }, 0\n );\n }\n };\n\n $scope.onSelect = function (node) {\n if (angular.isDefined($scope.tree)) {\n if (node !== $scope.tree.selected_node) {\n $scope.tree.select_node(node);\n }\n\n if (angular.isFunction($scope.tree.on_select)) {\n setTimeout(\n function () {\n $scope.tree.on_select(node);\n }, 0\n );\n }\n }\n };\n\n var passedExpand, _clone;\n $scope.toggleExpand = function (node, fnCallback) {\n passedExpand = true;\n if (angular.isFunction(fnCallback) && !fnCallback(node)) {\n passedExpand = false;\n } else if (angular.isFunction($scope.$callbacks.expand) && !$scope.$callbacks.expand(node)) {\n passedExpand = false;\n }\n\n if (passedExpand) {\n if (node.__children__.length > 0) {\n node.__expanded__ = !node.__expanded__;\n }\n }\n };\n\n\n var _fnGetHash = function (node) {\n return '#' + node.__parent__ + '#' + node[$scope.primary_key];\n },\n _fnSetHash = function (node) {\n var _hashKey = _fnGetHash(node);\n if (angular.isUndefinedOrNull(node.__hashKey__) || node.__hashKey__ !== _hashKey) {\n node.__hashKey__ = _hashKey;\n }\n return node;\n };\n $scope.getHash = _fnGetHash;\n $scope.$callbacks = {\n getHash: _fnGetHash,\n setHash: _fnSetHash,\n for_all_descendants: $scope.for_all_descendants,\n /*expand: function (node) {\n return true;\n },*/\n accept: function (/*dragInfo, moveTo, isChanged*/) {\n return $scope.dropEnabled === true;\n },\n calsIndent: function (level, skipUnit, skipEdge) {\n var unit = 0,\n edge = skipEdge ? 0 : $scope.indent_plus;\n if (!skipUnit) {\n unit = $scope.indent_unit ? $scope.indent_unit : 'px';\n }\n\n if (level - 1 < 1) {\n return edge + unit;\n } else {\n return $scope.indent * (level - 1) + edge + unit;\n }\n },\n droppable: function () {\n return $scope.dropEnabled === true;\n },\n draggable: function () {\n return $scope.dragEnabled === true;\n },\n beforeDrop: function (/*event*/) {\n return true;\n },\n changeKey: function (node) {\n var _key = node.__uid__;\n node.__uid__ = Math.random();\n if (node.__selected__) {\n delete node.__selected__;\n }\n\n if ($scope.primary_key !== '__uid__') {\n _key = '' + node[$scope.primary_key];\n _key = _key.replace(/_#.+$/g, '') + '_#' + node.__uid__;\n\n node[$scope.primary_key] = _key;\n }\n // delete(node.__hashKey__);\n },\n clone: function (node/*, _this*/) {\n _clone = angular.copy(node);\n this.for_all_descendants(_clone, this.changeKey);\n return _clone;\n },\n remove: function (node, parent, _this, delayReload) {\n var temp = parent.splice(node.__index__, 1)[0];\n if (!delayReload) {\n $scope.reload_data();\n }\n return temp;\n },\n clearInfo: function (node) {\n delete node.__inited__;\n delete node.__visible__;\n\n // always changed after call reload_data\n //delete node.__hashKey__;\n },\n add: function (node, pos, parent/*, _this*/) {\n // clearInfo\n this.for_all_descendants(node, this.clearInfo);\n if (parent) {\n if (parent.length > -1) {\n if (pos > -1) {\n parent.splice(pos, 0, node);\n } else {\n // todo If children need load crazy\n parent.push(node);\n }\n } else {\n parent.push(node);\n }\n }\n }\n };\n\n $scope.deleteScope = function (scope, node) {\n var _hash = node.__hashKey__;\n if ($scope.$globals[_hash] && $scope.$globals[_hash] === scope) {\n delete $scope.$globals[_hash];\n }\n };\n\n $scope.setScope = function (scope, node) {\n var _hash = node.__hashKey__;\n if ($scope.$globals[_hash] !== scope) {\n $scope.$globals[_hash] = scope;\n }\n };\n\n $scope.getScope = function (node) {\n if (node) {\n var _hash = node.__hashKey__;\n //var _hash = typeof node === 'string' ? node : node.__hashKey__;\n return $scope.$globals[_hash];\n }\n return $scope;\n };\n\n if ($attrs.enableDrag || $attrs.enableDrop) {\n $scope.placeElm = null;\n // $scope.dragBorder = 30;\n $scope.dragEnabled = null;\n $scope.dropEnabled = null;\n $scope.horizontal = null;\n\n if ($attrs.enableDrag) {\n\n $scope.dragDelay = 0;\n $scope.enabledMove = true;\n $scope.statusMove = true;\n $scope.enabledHotkey = false;\n $scope.enabledCollapse = null;\n $scope.statusElm = null;\n $scope.dragging = null;\n\n angular.extend(\n $scope.$callbacks, {\n beforeDrag: function (/*scopeDrag*/) {\n return true;\n },\n dragStop: function (info, passed) {\n if (!info || !info.changed && info.drag.enabledMove || !passed) {\n return null;\n }\n\n info.target.reload_data();\n\n if (info.target !== info.drag && info.drag.enabledMove) {\n info.drag.reload_data();\n }\n },\n dropped: function (info/*, pass*/) {\n if (!info) {\n return null;\n }\n\n var _node = info.node,\n _nodeAdd = null,\n _move = info.move,\n _parent = null,\n _parentRemove = info.parent || info.drag.treeData,\n _parentAdd = _move.parent || info.target.treeData,\n isMove = info.drag.enabledMove;\n\n if (!info.changed && isMove) {\n return false;\n }\n\n if (info.target.$callbacks.accept(info, info.move, info.changed)) {\n if (isMove) {\n _parent = _parentRemove;\n if (angular.isDefined(_parent.__children__)) {\n _parent = _parent.__children__;\n }\n\n _nodeAdd = info.drag.$callbacks.remove(\n _node,\n _parent,\n info.drag.$callbacks,\n true // delay reload\n );\n } else {\n _nodeAdd = info.drag.$callbacks.clone(_node, info.drag.$callbacks);\n }\n\n // if node dragging change index in sample node parent\n // and index node decrement\n if (isMove &&\n info.drag === info.target &&\n _parentRemove === _parentAdd &&\n _move.pos >= info.node.__index__) {\n _move.pos--;\n }\n\n _parent = _parentAdd;\n if (_parent.__children__) {\n _parent = _parent.__children__;\n }\n\n info.target.$callbacks.add(\n _nodeAdd,\n _move.pos,\n _parent,\n info.drag.$callbacks\n );\n\n return true;\n }\n\n return false;\n },\n dragStart: function (event) {\n },\n dragMove: function (event) {\n }\n }\n );\n\n $scope.setDragging = function (dragInfo) {\n $scope.dragging = dragInfo;\n };\n\n $scope.enableMove = function (val) {\n if (typeof val === 'boolean') {\n $scope.enabledMove = val;\n } else {\n $scope.enabledMove = true;\n }\n };\n\n if ($attrs.enableStatus) {\n $scope.enabledStatus = false;\n\n $scope.hideStatus = function () {\n if ($scope.statusElm) {\n $scope.statusElm.addClass($scope.$class.hidden);\n }\n };\n\n $scope.refreshStatus = function () {\n if (!$scope.dragging) {\n return;\n }\n\n if ($scope.enabledStatus) {\n var statusElmOld = $scope.statusElm;\n if ($scope.enabledMove) {\n $scope.statusElm = angular.element($TreeDnDTemplate.getMove($scope));\n } else {\n $scope.statusElm = angular.element($TreeDnDTemplate.getCopy($scope));\n }\n\n if (statusElmOld !== $scope.statusElm) {\n if (statusElmOld) {\n $scope.statusElm.attr('class', statusElmOld.attr('class'));\n $scope.statusElm.attr('style', statusElmOld.attr('style'));\n statusElmOld.remove();\n }\n $document.find('body').append($scope.statusElm);\n\n }\n\n $scope.statusElm.removeClass($scope.$class.hidden);\n }\n };\n\n $scope.setPositionStatus = function (e) {\n if ($scope.statusElm) {\n $scope.statusElm.css(\n {\n 'left': e.pageX + 10 + 'px',\n 'top': e.pageY + 15 + 'px',\n 'z-index': 9999\n }\n );\n $scope.statusElm.addClass($scope.$class.status);\n }\n };\n }\n }\n\n $scope.targeting = false;\n\n $scope.getPrevSibling = function (node) {\n if (node && node.__index__ > 0) {\n var _parent, _index = node.__index__ - 1;\n\n if (angular.isDefined(node.__parent_real__)) {\n _parent = $scope.tree_nodes[node.__parent_real__];\n return _parent.__children__[_index];\n }\n return $scope.treeData[_index];\n\n }\n return null;\n };\n\n $scope.getNode = function (index) {\n if (angular.isUndefinedOrNull(index)) {\n return null;\n }\n return $scope.tree_nodes[index];\n };\n\n $scope.initPlace = function (element, dragElm) {\n\n if (!$scope.placeElm) {\n if ($scope.isTable) {\n $scope.placeElm = angular.element($window.document.createElement('tr'));\n var _len_down = $scope.colDefinitions.length;\n $scope.placeElm.append(\n angular.element($window.document.createElement('td'))\n .addClass($scope.$class.empty)\n .addClass('indented')\n .addClass($scope.$class.place)\n );\n while (_len_down-- > 0) {\n $scope.placeElm.append(\n angular.element($window.document.createElement('td'))\n .addClass($scope.$class.empty)\n .addClass($scope.$class.place)\n );\n }\n } else {\n $scope.placeElm = angular.element($window.document.createElement('li'))\n .addClass($scope.$class.empty)\n .addClass($scope.$class.place);\n }\n\n }\n\n if (dragElm) {\n $scope.placeElm.css('height', $TreeDnDHelper.height(dragElm) + 'px');\n }\n\n if (element) {\n element[0].parentNode.insertBefore($scope.placeElm[0], element[0]);\n } else {\n $scope.getElementChilds().append($scope.placeElm);\n }\n\n return $scope.placeElm;\n };\n\n $scope.hidePlace = function () {\n if ($scope.placeElm) {\n $scope.placeElm.addClass($scope.$class.hidden);\n }\n };\n\n $scope.showPlace = function () {\n if ($scope.placeElm) {\n $scope.placeElm.removeClass($scope.$class.hidden);\n }\n };\n\n $scope.getScopeTree = function () {\n return $scope;\n };\n\n }\n\n $scope.$safeApply = $safeApply;\n\n $scope.hiddenChild = function fnHiddenChild(node, parent) {\n var nodeScope = $scope.getScope(node);\n if (nodeScope) {\n if (parent && parent.__expanded__ && parent.__visible__) {\n nodeScope.$element.removeClass($scope.$class.hidden);\n node.__visible__ = true;\n } else {\n nodeScope.$element.addClass($scope.$class.hidden);\n node.__visible__ = false;\n }\n } else {\n // show node & init scope\n if (parent && parent.__expanded__ && parent.__visible__) {\n node.__visible__ = true;\n } else {\n node.__visible__ = false;\n }\n }\n\n // skip all child hiding... if not expaned\n return node.__expanded__ === false;\n };\n\n var _fnInitFilter,\n _fnInitOrderBy,\n _fnGetControl,\n _defaultFilterOption = {\n showParent: true,\n showChild: false,\n beginAnd: true\n },\n tree,\n _watches = [\n [\n 'enableDrag',\n [\n ['boolean', 'enableStatus', null, 'enabledStatus'],\n ['boolean', 'enableMove', null, 'enabledMove'],\n ['number', 'dragDelay', 0, null, 0],\n ['boolean', 'enableCollapse', null, 'enabledCollapse'],\n ['boolean', 'enableHotkey', null, 'enabledHotkey', null, function (isHotkey) {\n if (isHotkey) {\n $scope.enabledMove = false;\n } else {\n $scope.enabledMove = $scope.statusMove;\n }\n }]\n ]\n ],\n [\n ['enableDrag', 'enableStatus'], [\n ['string', 'templateCopy', $attrs.templateCopy, 'templateCopy', null, function (_url) {\n if (_url && $templateCache.get(_url)) {\n $TreeDnDTemplate.setCopy(_url, $scope);\n }\n }],\n ['string', 'templateMove', $attrs.templateMove, 'templateMove', null, function (_url) {\n if (_url && $templateCache.get(_url)) {\n $TreeDnDTemplate.setMove(_url, $scope);\n }\n }]\n ]],\n [\n [['enableDrag', 'enableDrop']], [\n ['number', 'dragBorder', 30, 'dragBorder', 30]]\n ],\n [\n '*', [\n ['boolean', 'treeTable', true, 'treeTable', null],\n ['boolean', 'horizontal'],\n ['callback', 'treeClass', function (val) {\n switch (typeof val) {\n case 'string':\n $scope.$tree_class = val;\n break;\n case 'object':\n angular.extend($scope.$class, val);\n $scope.$tree_class = $scope.$class.tree;\n break;\n default:\n $scope.$tree_class = $attrs.treeClass;\n break;\n }\n }, 'treeClass', function () {\n $scope.$tree_class = $scope.$class.tree + ' table';\n }, null, function () {\n if (/^(\\s+[\\w\\-]+){2,}$/g.test(' ' + $attrs.treeClass)) {\n $scope.$tree_class = $attrs.treeClass.trim();\n return true;\n }\n }],\n [\n ['object', 'string'], 'expandOn', getExpandOn, 'expandingProperty', getExpandOn,\n function (expandOn) {\n if (angular.isUndefinedOrNull(expandOn)) {\n $scope.expandingProperty = $attrs.expandOn;\n }\n }],\n ['object', 'treeControl', angular.isDefined($scope.tree) ? $scope.tree : {},\n 'tree', null, function ($tree) {\n\n if (!angular.isFunction(_fnGetControl)) {\n _fnGetControl = $TreeDnDPlugin('$TreeDnDControl');\n }\n\n if (angular.isFunction(_fnGetControl)) {\n tree = angular.extend(\n $tree,\n _fnGetControl($scope)\n );\n }\n }],\n [\n ['array', 'object'], 'columnDefs', getColDefs, 'colDefinitions', getColDefs,\n function (colDefs) {\n if (angular.isUndefinedOrNull(colDefs) || !angular.isArray(colDefs)) {\n $scope.colDefinitions = getColDefs();\n }\n }],\n [\n ['object', 'string', 'array', 'function'], 'orderBy', $attrs.orderBy\n ],\n [\n ['object', 'array'], 'filter', null, 'filter', null, function (filters) {\n var _passed = false;\n if (angular.isDefined(filters) && !angular.isArray(filters)) {\n var _keysF = Object.keys(filters),\n _lenF = _keysF.length, _iF;\n\n if (_lenF > 0) {\n for (_iF = 0; _iF < _lenF; _iF++) {\n\n if (typeof filters[_keysF[_iF]] === 'string' &&\n filters[_keysF[_iF]].length === 0) {\n continue;\n }\n _passed = true;\n break;\n }\n }\n }\n\n $scope.enabledFilter = _passed;\n reload_data();\n }],\n [\n 'object', 'filterOptions', _defaultFilterOption, 'filterOptions',\n _defaultFilterOption, function (option) {\n if (angular.isObject(option)) {\n $scope.filterOptions = angular.extend(_defaultFilterOption, option);\n }\n }],\n ['string', 'primaryKey', $attrs.primaryKey, 'primary_key', '__uid__'],\n ['string', 'indentUnit', $attrs.indentUnit, 'indent_unit'],\n ['number', 'indent', 30, null, 30],\n ['number', 'indentPlus', 20, null, 20],\n ['null', 'callbacks', function (optCallbacks) {\n angular.forEach(\n optCallbacks, function (value, key) {\n if (typeof value === 'function') {\n if ($scope.$callbacks[key]) {\n $scope.$callbacks[key] = value;\n }\n }\n }\n );\n return $scope.$callbacks;\n },\n '$callbacks'\n ],\n ['number', 'expandLevel', 3, 'expandLevel', 3, function () {\n reload_data();\n }],\n ['number', 'treeLimit', 100, '$TreeLimit', 100],\n ['boolean', 'enableDrag', null, 'dragEnabled'],\n ['boolean', 'enableDrop', null, 'dropEnabled']\n ]]\n ],\n w, lenW = _watches.length,\n i, len,\n _curW,\n _typeW, _nameW, _defaultW, _scopeW, _NotW, _AfterW, _BeforeW,\n\n // debounce reload_Data;\n timeReloadData, tmpTreeData;\n\n for (w = 0; w < lenW; w++) {\n // skip if not exist\n if (!check_exist_attr($attrs, _watches[w][0], true)) {\n continue;\n }\n _curW = _watches[w][1];\n for (i = 0, len = _curW.length; i < len; i++) {\n _typeW = _curW[i][0];\n _nameW = _curW[i][1];\n _defaultW = _curW[i][2];\n _scopeW = _curW[i][3];\n _NotW = _curW[i][4];\n _AfterW = _curW[i][5];\n _BeforeW = _curW[i][6];\n generateWatch(_typeW, _nameW, _defaultW, _scopeW, _NotW, _AfterW, _BeforeW);\n }\n }\n\n if ($attrs.treeData) {\n $scope.$watch(\n $attrs.treeData, function (val) {\n if (angular.equals(val, $scope.treeData)) {\n return;\n }\n\n tmpTreeData = val;\n if (angular.isUndefinedOrNull(timeReloadData)) {\n timeReloadData = $timeout(timeLoadData, 350);\n }\n }, true\n );\n }\n\n function timeLoadData() {\n $scope.treeData = tmpTreeData;\n reload_data();\n timeReloadData = null;\n }\n\n $scope.updateLimit = function updateLimit() {\n //console.log('Call fn UpdateLimit');\n $scope.$TreeLimit += 50;\n };\n\n $scope.reload_data = reload_data;\n\n function check_exist_attr(attrs, existAttr, isAnd) {\n if (angular.isUndefinedOrNull(existAttr)) {\n return false;\n }\n\n if (existAttr === '*' || !angular.isUndefined(attrs[existAttr])) {\n return true;\n }\n\n if (angular.isArray(existAttr)) {\n return for_each_attrs(attrs, existAttr, isAnd);\n }\n }\n\n function for_each_attrs(attrs, exist, isAnd) {\n var i, len = exist.length, passed = false;\n\n if (len === 0) {\n return null;\n }\n for (i = 0; i < len; i++) {\n if (check_exist_attr(attrs, exist[i], !isAnd)) {\n passed = true;\n if (!isAnd) {\n return true;\n }\n } else {\n if (isAnd) {\n return false;\n }\n }\n }\n\n return passed;\n }\n\n function generateWatch(type, nameAttr, valDefault, nameScope, fnNotExist, fnAfter,\n fnBefore) {\n nameScope = nameScope || nameAttr;\n if (typeof type === 'string' || angular.isArray(type)) {\n if (angular.isFunction(fnBefore) && fnBefore()) {\n return;//jmp\n }\n if (typeof $attrs[nameAttr] === 'string') {\n $scope.$watch(\n $attrs[nameAttr], function (val) {\n if (typeof type === 'string' && typeof val === type ||\n angular.isArray(type) && type.indexOf(typeof val) > -1\n ) {\n $scope[nameScope] = val;\n } else {\n if (angular.isFunction(valDefault)) {\n $scope[nameScope] = valDefault(val);\n } else {\n $scope[nameScope] = valDefault;\n }\n }\n\n if (angular.isFunction(fnAfter)) {\n fnAfter($scope[nameScope], $scope);\n }\n }, true\n );\n } else {\n\n if (angular.isFunction(fnNotExist)) {\n $scope[nameScope] = fnNotExist();\n } else if (!angular.isUndefined(fnNotExist)) {\n $scope[nameScope] = fnNotExist;\n }\n }\n }\n }\n\n function $safeApply(fn) {\n var phase = this.$root.$$phase;\n if (phase === '$apply' || phase === '$digest') {\n if (fn && typeof fn === 'function') {\n fn();\n }\n } else {\n this.$apply(fn);\n }\n }\n\n function getExpandOn() {\n if ($scope.treeData && $scope.treeData.length) {\n var _firstNode = $scope.treeData[0], _keys = Object.keys(_firstNode),\n _regex = new RegExp('^__([a-zA-Z0-9_\\-]*)__$'),\n _len,\n i;\n // Auto get first field with type is string;\n for (i = 0, _len = _keys.length; i < _len; i++) {\n if (typeof _firstNode[_keys[i]] === 'string' && !_regex.test(_keys[i])) {\n $scope.expandingProperty = _keys[i];\n return;\n }\n }\n\n // Auto get first\n if (angular.isUndefinedOrNull($scope.expandingProperty)) {\n $scope.expandingProperty = _keys[0];\n }\n\n }\n }\n\n function getColDefs() {\n // Auto get Defs except attribute __level__ ....\n if ($scope.treeData.length) {\n var _col_defs = [], _firstNode = $scope.treeData[0],\n _regex = new RegExp('(^__([a-zA-Z0-9_\\-]*)__$|^' + $scope.expandingProperty + '$)'),\n _keys = Object.keys(_firstNode),\n i, _len;\n // Auto get first field with type is string;\n for (i = 0, _len = _keys.length; i < _len; i++) {\n if (typeof _firstNode[_keys[i]] === 'string' && !_regex.test(_keys[i])) {\n _col_defs.push(\n {\n field: _keys[i]\n }\n );\n }\n }\n $scope.colDefinitions = _col_defs;\n }\n }\n\n function do_f(root, node, parent, parent_real, level, visible, index) {\n\n if (typeof node !== 'object') {\n return 0;\n }\n\n var _i, _len, _icon, _index_real, _dept, _hashKey;\n if (!angular.isArray(node.__children__)) {\n node.__children__ = [];\n }\n\n node.__parent_real__ = parent_real;\n node.__parent__ = parent;\n _len = node.__children__.length;\n\n if (angular.isUndefinedOrNull(node.__expanded__) && _len > 0) {\n node.__expanded__ = level < $scope.expandLevel;\n }\n\n if (_len === 0) {\n _icon = -1;\n } else {\n if (node.__expanded__) {\n _icon = 1;\n } else {\n _icon = 0;\n }\n }\n\n // Insert item vertically\n _index_real = root.length;\n node.__index__ = index;\n node.__index_real__ = _index_real;\n node.__level__ = level;\n node.__icon__ = _icon;\n node.__icon_class__ = $scope.$class.icon[_icon];\n node.__visible__ = !!visible;\n\n if (angular.isUndefinedOrNull(node.__uid__)) {\n node.__uid__ = '' + Math.random();\n }\n\n _hashKey = $scope.getHash(node);\n\n if (angular.isUndefinedOrNull(node.__hashKey__) || node.__hashKey__ !== _hashKey) {\n node.__hashKey__ = _hashKey;\n }\n\n root.push(node);\n\n // Check node children\n _dept = 1;\n if (_len > 0) {\n for (_i = 0; _i < _len; _i++) {\n _dept += do_f(\n root,\n node.__children__[_i],\n node[$scope.primary_key],\n _index_real,\n level + 1,\n visible && node.__expanded__,\n _i\n );\n }\n }\n\n node.__dept__ = _dept;\n\n return _dept;\n }\n\n function init_data(data) {\n\n // clear memory\n if (angular.isDefined($scope.tree_nodes)) {\n delete $scope.tree_nodes;\n }\n\n $scope.tree_nodes = data;\n return data;\n }\n\n function reload_data(oData) {\n var _data,\n _len,\n _tree_nodes = [];\n if (angular.isDefined(oData)) {\n if (!angular.isArray(oData) || oData.length === 0) {\n return init_data([]);\n } else {\n _data = oData;\n }\n } else if (!angular.isArray($scope.treeData) || $scope.treeData.length === 0) {\n return init_data([]);\n } else {\n _data = $scope.treeData;\n }\n\n if (!$attrs.expandOn) {\n getExpandOn();\n }\n\n if (!$attrs.columnDefs) {\n getColDefs();\n }\n\n if (angular.isDefined($scope.orderBy)) {\n if (!angular.isFunction(_fnInitOrderBy)) {\n _fnInitOrderBy = $TreeDnDPlugin('$TreeDnDOrderBy');\n }\n\n if (angular.isFunction(_fnInitOrderBy)) {\n _data = _fnInitOrderBy(_data, $scope.orderBy);\n }\n }\n\n if (angular.isDefined($scope.filter)) {\n if (!angular.isFunction(_fnInitFilter)) {\n _fnInitFilter = $TreeDnDPlugin('$TreeDnDFilter');\n }\n\n if (angular.isFunction(_fnInitFilter)) {\n _data = _fnInitFilter(_data, $scope.filter, $scope.filterOptions);\n }\n }\n\n _len = _data.length;\n if (_len > 0) {\n var _i,\n _deptTotal = 0;\n\n for (_i = 0; _i < _len; _i++) {\n _deptTotal += do_f(_tree_nodes, _data[_i], null, null, 1, true, _i);\n }\n\n }\n\n init_data(_tree_nodes);\n\n return _tree_nodes;\n }\n }\n\n function fnCompile(tElement) {\n\n var $_Template = '',\n _element = tElement.html().trim();\n\n if (_element.length > 0) {\n $_Template = _element;\n tElement.html('');\n }\n\n return function fnPost(scope, element, attrs) {\n\n if (attrs.enableDrag) {\n var _fnInitDrag = $TreeDnDPlugin('$TreeDnDDrag');\n if (angular.isFunction(_fnInitDrag)) {\n _fnInitDrag(scope, element, $window, $document);\n }\n }\n\n // kick out $digest\n element.ready(function () {\n // apply Template\n function checkTreeTable(template, scope) {\n var elemNode = template[0].querySelector('[tree-dnd-node]'),\n attrInclude;\n\n scope.isTable = null;\n if (elemNode) {\n elemNode = angular.element(elemNode);\n attrInclude = elemNode.attr('ng-include');\n } else {\n return;\n }\n\n if (attrInclude) {\n var treeInclude = $parse(attrInclude)(scope) || attrInclude;\n if (typeof treeInclude === 'string') {\n return $http.get(\n treeInclude,\n {cache: $templateCache}\n ).then(function (response) {\n var data = response.data || '';\n data = data.trim();\n //scope.templateNode = data;\n var tempDiv = document.createElement('div');\n tempDiv.innerHTML = data;\n tempDiv = angular.element(tempDiv);\n scope.isTable = !tempDiv[0].querySelector('[tree-dnd-nodes]');\n }\n );\n }\n } else {\n scope.isTable = !elemNode[0].querySelector('[tree-dnd-nodes]');\n //scope.templateNode = elemNode.html();\n }\n $TreeDnDViewport.setTemplate(scope, scope.templateNode);\n //elemNode.html('');\n }\n\n //scope.$watch(tableDataLoaded, transformTable);\n /*\n function tableDataLoaded(elem) {\n // first cell in the tbody exists when data is loaded but doesn't have a width\n // until after the table is transformed\n var firstCell = elem.querySelector('tbody tr:first-child td:first-child');\n return firstCell && !firstCell.style.width;\n }\n\n function transformTable(elem, attrs) {\n // reset display styles so column widths are correct when measured below\n angular.element(elem.querySelectorAll('thead, tbody, tfoot')).css('display', '');\n\n // wrap in $timeout to give table a chance to finish rendering\n $timeout(function () {\n // set widths of columns\n angular.forEach(elem.querySelectorAll('tr:first-child th'), function (thElem, i) {\n\n var tdElems = elem.querySelector('tbody tr:first-child td:nth-child(' + (i + 1) + ')');\n var tfElems = elem.querySelector('tfoot tr:first-child td:nth-child(' + (i + 1) + ')');\n\n var columnWidth = tdElems ? tdElems.offsetWidth : thElem.offsetWidth;\n if (tdElems) {\n tdElems.style.width = columnWidth + 'px';\n }\n if (thElem) {\n thElem.style.width = columnWidth + 'px';\n }\n if (tfElems) {\n tfElems.style.width = columnWidth + 'px';\n }\n });\n\n // set css styles on thead and tbody\n angular.element(elem.querySelectorAll('thead, tfoot')).css('display', 'block');\n\n angular.element(elem.querySelectorAll('tbody')).css({\n 'display': 'block',\n 'height': attrs.tableHeight || 'inherit',\n 'overflow': 'auto'\n });\n\n // reduce width of last column by width of scrollbar\n var tbody = elem.querySelector('tbody');\n var scrollBarWidth = tbody.offsetWidth - tbody.clientWidth;\n if (scrollBarWidth > 0) {\n // for some reason trimming the width by 2px lines everything up better\n scrollBarWidth -= 2;\n var lastColumn = elem.querySelector('tbody tr:first-child td:last-child');\n lastColumn.style.width = lastColumn.offsetWidth - scrollBarWidth + 'px';\n }\n });\n }*/\n var promiseCheck;\n if ($_Template.length > 0) {\n promiseCheck = checkTreeTable(angular.element($_Template.trim()), scope);\n if (angular.isObject(promiseCheck)) {\n promiseCheck.then(function () {\n element.append($compile($_Template)(scope));\n });\n } else {\n element.append($compile($_Template)(scope));\n }\n } else {\n $http.get(\n attrs.templateUrl || $TreeDnDTemplate.getPath(),\n {cache: $templateCache}\n ).then(function (response) {\n var data = response.data || '';\n data = angular.element(data.trim());\n promiseCheck = checkTreeTable(data, scope);\n if (angular.isObject(promiseCheck)) {\n promiseCheck.then(function () {\n element.append($compile(data)(scope));\n });\n } else {\n element.append($compile(data)(scope));\n }\n }\n );\n }\n })\n };\n }\n}\n\n\nangular.module('ntt.TreeDnD')\n .factory('$TreeDnDConvert', function () {\n var _$initConvert = {\n line2tree: function (data, primaryKey, parentKey, callback) {\n callback = typeof callback === 'function' ? callback : function () {\n };\n if (!data || data.length === 0 || !primaryKey || !parentKey) {\n return [];\n }\n var tree = [],\n rootIds = [],\n item = data[0],\n _primary = item[primaryKey],\n treeObjs = {},\n parentId, parent,\n len = data.length,\n i = 0;\n\n while (i < len) {\n item = data[i++];\n callback(item);\n _primary = item[primaryKey];\n treeObjs[_primary] = item;\n }\n i = 0;\n while (i < len) {\n item = data[i++];\n callback(item);\n _primary = item[primaryKey];\n treeObjs[_primary] = item;\n parentId = item[parentKey];\n if (parentId) {\n parent = treeObjs[parentId];\n if (parent) {\n if (parent.__children__) {\n parent.__children__.push(item);\n } else {\n parent.__children__ = [item];\n }\n }\n } else {\n rootIds.push(_primary);\n }\n }\n len = rootIds.length;\n for (i = 0; i < len; i++) {\n tree.push(treeObjs[rootIds[i]]);\n }\n return tree;\n },\n tree2tree: function access_child(data, containKey, callback) {\n callback = typeof callback === 'function' ? callback : function () {\n };\n var _tree = [],\n _i,\n _len = data ? data.length : 0,\n _copy, _child;\n for (_i = 0; _i < _len; _i++) {\n _copy = angular.copy(data[_i]);\n callback(_copy);\n if (angular.isArray(_copy[containKey]) && _copy[containKey].length > 0) {\n _child = access_child(_copy[containKey], containKey, callback);\n delete _copy[containKey];\n _copy.__children__ = _child;\n }\n _tree.push(_copy);\n }\n return _tree;\n }\n };\n\n return _$initConvert;\n });\n\nangular.module('ntt.TreeDnD')\n .factory('$TreeDnDHelper', [\n '$document', '$window',\n function ($document, $window) {\n var _$helper = {\n nodrag: function (targetElm) {\n return typeof targetElm.attr('data-nodrag') !== 'undefined';\n },\n eventObj: function (e) {\n var obj = e;\n if (e.targetTouches !== undefined) {\n obj = e.targetTouches.item(0);\n } else if (e.originalEvent !== undefined && e.originalEvent.targetTouches !== undefined) {\n obj = e.originalEvent.targetTouches.item(0);\n }\n return obj;\n },\n dragInfo: function (scope) {\n var _node = scope.getData(),\n _tree = scope.getScopeTree(),\n _parent = scope.getNode(_node.__parent_real__);\n\n return {\n node: _node,\n parent: _parent,\n move: {\n parent: _parent,\n pos: _node.__index__\n },\n scope: scope,\n target: _tree,\n drag: _tree,\n drop: scope.getPrevSibling(_node),\n changed: false\n };\n },\n height: function (element) {\n return element.prop('scrollHeight');\n },\n width: function (element) {\n return element.prop('scrollWidth');\n },\n offset: function (element) {\n var boundingClientRect = element[0].getBoundingClientRect();\n return {\n width: element.prop('offsetWidth'),\n height: element.prop('offsetHeight'),\n top: boundingClientRect.top + ($window.pageYOffset || $document[0].body.scrollTop || $document[0].documentElement.scrollTop),\n left: boundingClientRect.left + ($window.pageXOffset || $document[0].body.scrollLeft || $document[0].documentElement.scrollLeft)\n };\n },\n positionStarted: function (e, target) {\n return {\n offsetX: e.pageX - this.offset(target).left,\n offsetY: e.pageY - this.offset(target).top,\n startX: e.pageX,\n lastX: e.pageX,\n startY: e.pageY,\n lastY: e.pageY,\n nowX: 0,\n nowY: 0,\n distX: 0,\n distY: 0,\n dirAx: 0,\n dirX: 0,\n dirY: 0,\n lastDirX: 0,\n lastDirY: 0,\n distAxX: 0,\n distAxY: 0\n };\n },\n positionMoved: function (e, pos, firstMoving) {\n // mouse position last events\n pos.lastX = pos.nowX;\n pos.lastY = pos.nowY;\n\n // mouse position this events\n pos.nowX = e.pageX;\n pos.nowY = e.pageY;\n\n // distance mouse moved between events\n pos.distX = pos.nowX - pos.lastX;\n pos.distY = pos.nowY - pos.lastY;\n\n // direction mouse was moving\n pos.lastDirX = pos.dirX;\n pos.lastDirY = pos.dirY;\n\n // direction mouse is now moving (on both axis)\n pos.dirX = pos.distX === 0 ? 0 : pos.distX > 0 ? 1 : -1;\n pos.dirY = pos.distY === 0 ? 0 : pos.distY > 0 ? 1 : -1;\n\n // axis mouse is now moving on\n var newAx = Math.abs(pos.distX) > Math.abs(pos.distY) ? 1 : 0;\n\n // do nothing on first move\n if (firstMoving) {\n pos.dirAx = newAx;\n pos.moving = true;\n return;\n }\n\n // calc distance moved on this axis (and direction)\n if (pos.dirAx !== newAx) {\n pos.distAxX = 0;\n pos.distAxY = 0;\n } else {\n pos.distAxX += Math.abs(pos.distX);\n if (pos.dirX !== 0 && pos.dirX !== pos.lastDirX) {\n pos.distAxX = 0;\n }\n pos.distAxY += Math.abs(pos.distY);\n if (pos.dirY !== 0 && pos.dirY !== pos.lastDirY) {\n pos.distAxY = 0;\n }\n }\n pos.dirAx = newAx;\n },\n replaceIndent: function (scope, element, indent, attr) {\n attr = attr || 'left';\n angular.element(element.children()[0]).css(attr, scope.$callbacks.calsIndent(indent));\n }\n };\n\n return _$helper;\n }]\n );\n\nangular.module('ntt.TreeDnD')\n .factory('$TreeDnDPlugin', [\n '$injector',\n function ($injector) {\n var _fnget = function (name) {\n if (angular.isDefined($injector) && $injector.has(name)) {\n return $injector.get(name);\n }\n return null;\n };\n return _fnget;\n }]\n );\n\nangular.module('ntt.TreeDnD')\n .factory('$TreeDnDTemplate', [\n '$templateCache',\n function ($templateCache) {\n var templatePath = 'template/TreeDnD/TreeDnD.html',\n copyPath = 'template/TreeDnD/TreeDnDStatusCopy.html',\n movePath = 'template/TreeDnD/TreeDnDStatusMove.html',\n scopes = {},\n temp,\n _$init = {\n setMove: function (path, scope) {\n if (!scopes[scope.$id]) {\n scopes[scope.$id] = {};\n }\n scopes[scope.$id].movePath = path;\n },\n setCopy: function (path, scope) {\n if (!scopes[scope.$id]) {\n scopes[scope.$id] = {};\n }\n scopes[scope.$id].copyPath = path;\n },\n getPath: function () {\n return templatePath;\n },\n getCopy: function (scope) {\n if (scopes[scope.$id] && scopes[scope.$id].copyPath) {\n temp = $templateCache.get(scopes[scope.$id].copyPath);\n if (temp) {\n return temp;\n }\n }\n return $templateCache.get(copyPath);\n },\n getMove: function (scope) {\n if (scopes[scope.$id] && scopes[scope.$id].movePath) {\n temp = $templateCache.get(scopes[scope.$id].movePath);\n if (temp) {\n return temp;\n }\n }\n return $templateCache.get(movePath);\n }\n };\n\n return _$init;\n }]\n );\n\nangular.module('ntt.TreeDnD')\n .factory('$TreeDnDViewport', fnInitTreeDnDViewport);\n\nfnInitTreeDnDViewport.$inject = ['$window', '$document', '$timeout', '$q', '$compile'];\n\nfunction fnInitTreeDnDViewport($window, $document, $timeout, $q, $compile) {\n\n var viewport = null,\n isUpdating = false,\n isRender = false,\n updateAgain = false,\n viewportRect,\n items = [],\n nodeTemplate,\n updateTimeout,\n renderTime,\n $initViewport = {\n setViewport: setViewport,\n getViewport: getViewport,\n add: add,\n setTemplate: setTemplate,\n getItems: getItems,\n updateDelayed: updateDelayed\n },\n eWindow = angular.element($window);\n\n eWindow.on('load resize scroll', updateDelayed);\n\n return $initViewport;\n\n function update() {\n\n viewportRect = {\n width: eWindow.prop('offsetWidth') || document.documentElement.clientWidth,\n height: eWindow.prop('offsetHeight') || document.documentElement.clientHeight,\n top: $document[0].body.scrollTop || $document[0].documentElement.scrollTop,\n left: $document[0].body.scrollLeft || $document[0].documentElement.scrollLeft\n };\n\n if (isUpdating || isRender) {\n updateAgain = true;\n return;\n }\n isUpdating = true;\n\n recursivePromise();\n }\n\n function recursivePromise() {\n if (isRender) {\n return;\n }\n\n var number = number > 0 ? number : items.length, item;\n\n if (number > 0) {\n item = items[0];\n\n isRender = true;\n renderTime = $timeout(function () {\n //item.element.html(nodeTemplate);\n //$compile(item.element.contents())(item.scope);\n\n items.splice(0, 1);\n isRender = false;\n number--;\n $timeout.cancel(renderTime);\n recursivePromise();\n }, 0);\n\n } else {\n isUpdating = false;\n if (updateAgain) {\n updateAgain = false;\n update();\n }\n }\n\n }\n\n /**\n * Check if a point is inside specified bounds\n * @param x\n * @param y\n * @param bounds\n * @returns {boolean}\n */\n function pointIsInsideBounds(x, y, bounds) {\n return x >= bounds.left &&\n y >= bounds.top &&\n x <= bounds.left + bounds.width &&\n y <= bounds.top + bounds.height;\n }\n\n /**\n * @name setViewport\n * @desciption Set the viewport element\n * @param element\n */\n function setViewport(element) {\n viewport = element;\n }\n\n /**\n * Return the current viewport\n * @returns {*}\n */\n function getViewport() {\n return viewport;\n }\n\n /**\n * trigger an update\n */\n function updateDelayed() {\n $timeout.cancel(updateTimeout);\n updateTimeout = $timeout(function () {\n update();\n }, 0);\n }\n\n /**\n * Add listener for event\n * @param element\n * @param callback\n */\n function add(scope, element) {\n updateDelayed();\n items.push({\n element: element,\n scope: scope\n });\n }\n\n function setTemplate(scope, template) {\n nodeTemplate = template;\n }\n\n /**\n * Get list of items\n * @returns {Array}\n */\n function getItems() {\n return items;\n }\n}\n\nangular.module('ntt.TreeDnD')\n .factory('$TreeDnDFilter', [\n '$filter', function ($filter) {\n return fnInitFilter;\n\n function for_all_descendants(options, node, fieldChild, fnBefore, fnAfter, parentPassed) {\n if (!angular.isFunction(fnBefore)) {\n return null;\n }\n\n var _i, _len, _nodes,\n _nodePassed = fnBefore(options, node),\n _childPassed = false,\n _filter_index = options.filter_index;\n\n if (angular.isDefined(node[fieldChild])) {\n _nodes = node[fieldChild];\n _len = _nodes.length;\n\n options.filter_index = 0;\n for (_i = 0; _i < _len; _i++) {\n _childPassed = for_all_descendants(\n options,\n _nodes[_i],\n fieldChild,\n fnBefore,\n fnAfter,\n _nodePassed || parentPassed\n ) || _childPassed;\n }\n\n // restore filter_index of node\n options.filter_index = _filter_index;\n }\n\n if (angular.isFunction(fnAfter)) {\n fnAfter(options, node, _nodePassed === true, _childPassed === true, parentPassed === true);\n }\n\n return _nodePassed || _childPassed;\n }\n\n /**\n * Check data with callback\n * @param {string|object|function|regex} callback\n * @param {*} data\n * @returns {null|boolean}\n * @private\n */\n function _fnCheck(callback, data) {\n if (angular.isUndefinedOrNull(data) || angular.isArray(data)) {\n return null;\n }\n\n if (angular.isFunction(callback)) {\n return callback(data, $filter);\n } else {\n if (typeof callback === 'boolean') {\n data = !!data;\n return data === callback;\n } else if (angular.isDefined(callback)) {\n try {\n var _regex = new RegExp(callback);\n return _regex.test(data);\n }\n catch (err) {\n if (typeof data === 'string') {\n return data.indexOf(callback) > -1;\n } else {\n return null;\n }\n }\n } else {\n return null;\n }\n }\n }\n\n /**\n * `fnProcess` to call `_fnCheck`. If `condition` is `array` then call `for_each_filter`\n * else will call `_fnCheck`. Specical `condition.field` is `_$` then apply `condition.callback` for all field, if have `field` invaild then `return true`.\n *\n * @param node\n * @param condition\n * @param isAnd\n * @returns {null|boolean}\n * @private\n */\n function _fnProccess(node, condition, isAnd) {\n if (angular.isArray(condition)) {\n return for_each_filter(node, condition, isAnd);\n } else {\n var _key = condition.field,\n _callback = condition.callback,\n _iO, _keysO, _lenO;\n\n if (_key === '_$') {\n _keysO = Object.keys(node);\n _lenO = _keysO.length;\n for (_iO = 0; _iO < _lenO; _iO++) {\n if (_fnCheck(_callback, node[_keysO[_iO]])) {\n return true;\n }\n }\n } else if (angular.isDefined(node[_key])) {\n return _fnCheck(_callback, node[_key]);\n }\n }\n return null;\n }\n\n /**\n *\n * @param {object} node\n * @param {array} conditions Array `conditions`\n * @param {boolean} isAnd check with condition `And`, if `And` then `return false` when all `false`\n * @returns {null|boolean}\n */\n function for_each_filter(node, conditions, isAnd) {\n var i, len = conditions.length || 0, passed = false;\n if (len === 0) {\n return null;\n }\n\n for (i = 0; i < len; i++) {\n if (_fnProccess(node, conditions[i], !isAnd)) {\n passed = true;\n // if condition `or` then return;\n if (!isAnd) {\n return true;\n }\n } else {\n\n // if condition `and` and result in fnProccess = false then return;\n if (isAnd) {\n return false;\n }\n }\n }\n\n return passed;\n }\n\n /**\n * Will call _fnAfter to clear data no need\n * @param {object} options\n * @param {object} node\n * @param {boolean} isNodePassed\n * @param {boolean} isChildPassed\n * @param {boolean} isParentPassed\n * @private\n */\n function _fnAfter(options, node, isNodePassed, isChildPassed, isParentPassed) {\n if (isNodePassed === true) {\n node.__filtered__ = true;\n node.__filtered_visible__ = true;\n node.__filtered_index__ = options.filter_index++;\n return; //jmp\n } else if (isChildPassed === true && options.showParent === true\n || isParentPassed === true && options.showChild === true) {\n node.__filtered__ = false;\n node.__filtered_visible__ = true;\n node.__filtered_index__ = options.filter_index++;\n return; //jmp\n }\n\n // remove attr __filtered__\n delete node.__filtered__;\n delete node.__filtered_visible__;\n delete node.__filtered_index__;\n }\n\n /**\n * `fnBefore` will called when `for_all_descendants` of `node` checking.\n * If `filter` empty then return `true` else result of function `_fnProccess` {@see _fnProccess}\n *\n * @param {object} options\n * @param {object} node\n * @returns {null|boolean}\n * @private\n */\n function _fnBefore(options, node) {\n if (options.filter.length === 0) {\n return true;\n } else {\n return _fnProccess(node, options.filter, options.beginAnd || false);\n }\n }\n\n /**\n * `fnBeforeClear` will called when `for_all_descendants` of `node` checking.\n * Alway false to Clear Filter empty\n *\n * @param {object} options\n * @param {object} node\n * @returns {null|boolean}\n * @private\n */\n function _fnBeforeClear(options, node) {\n return false;\n }\n\n /**\n * `_fnConvert` to convert `filter` `object` to `array` invaild.\n *\n * @param {object|array} filters\n * @returns {array} Instead of `filter` or new array invaild *(converted from filter)*\n * @private\n */\n function _fnConvert(filters) {\n var _iF, _lenF, _keysF,\n _filter,\n _state;\n // convert filter object to array filter\n if (angular.isObject(filters) && !angular.isArray(filters)) {\n _keysF = Object.keys(filters);\n _lenF = _keysF.length;\n _filter = [];\n\n if (_lenF > 0) {\n for (_iF = 0; _iF < _lenF; _iF++) {\n\n if (typeof filters[_keysF[_iF]] === 'string' && filters[_keysF[_iF]].length === 0) {\n continue;\n } else if (angular.isArray(filters[_keysF[_iF]])) {\n _state = filters[_keysF[_iF]];\n } else if (angular.isObject(filters[_keysF[_iF]])) {\n _state = _fnConvert(filters[_keysF[_iF]]);\n } else {\n _state = {\n field: _keysF[_iF],\n callback: filters[_keysF[_iF]]\n };\n }\n _filter.push(_state);\n }\n }\n _state = null;\n return _filter;\n }\n else {\n return filters;\n }\n }\n\n /**\n * `fnInitFilter` function is constructor of service `$TreeDnDFilter`.\n * @constructor\n * @param {object|array} treeData\n * @param {object|array} filters\n * @param {object} options\n * @param {string} keyChild\n * @returns {array} Return `treeData` or `treeData` with `filter`\n * @private\n */\n function fnInitFilter(treeData, filters, options, keyChild) {\n if (!angular.isArray(treeData)\n || treeData.length === 0) {\n return treeData;\n }\n\n var _i, _len,\n _filter;\n\n _filter = _fnConvert(filters);\n if (!(angular.isArray(_filter) || angular.isObject(_filter))\n || _filter.length === 0) {\n for (_i = 0, _len = treeData.length; _i < _len; _i++) {\n for_all_descendants(\n options,\n treeData[_i],\n keyChild || '__children__',\n _fnBeforeClear, _fnAfter\n );\n }\n return treeData;\n }\n\n options.filter = _filter;\n options.filter_index = 0;\n for (_i = 0, _len = treeData.length; _i < _len; _i++) {\n for_all_descendants(\n options,\n treeData[_i],\n keyChild || '__children__',\n _fnBefore, _fnAfter\n );\n }\n\n return treeData;\n }\n\n }]\n );\n\nangular.module('ntt.TreeDnD')\n .factory('$TreeDnDOrderBy', [\n '$filter',\n function ($filter) {\n var _fnOrderBy = $filter('orderBy'),\n for_all_descendants = function for_all_descendants(options, node, name, fnOrderBy) {\n var _i, _len, _nodes;\n\n if (angular.isDefined(node[name])) {\n _nodes = node[name];\n _len = _nodes.length;\n // OrderBy children\n for (_i = 0; _i < _len; _i++) {\n _nodes[_i] = for_all_descendants(options, _nodes[_i], name, fnOrderBy);\n }\n\n node[name] = fnOrderBy(node[name], options);\n }\n return node;\n },\n _fnOrder = function _fnOrder(list, orderBy) {\n return _fnOrderBy(list, orderBy);\n },\n _fnMain = function _fnMain(treeData, orderBy) {\n if (!angular.isArray(treeData)\n || treeData.length === 0\n || !(angular.isArray(orderBy) || angular.isObject(orderBy) || angular.isString(orderBy) || angular.isFunction(orderBy))\n || orderBy.length === 0 && !angular.isFunction(orderBy)) {\n return treeData;\n }\n\n var _i, _len;\n\n for (_i = 0, _len = treeData.length; _i < _len; _i++) {\n treeData[_i] = for_all_descendants(\n orderBy,\n treeData[_i],\n '__children__',\n _fnOrder\n );\n }\n\n return _fnOrder(treeData, orderBy);\n };\n\n return _fnMain;\n }]\n );\n\nangular.module('ntt.TreeDnD')\n .factory('$TreeDnDDrag', [\n '$timeout', '$TreeDnDHelper',\n function ($timeout, $TreeDnDHelper) {\n function _fnPlaceHolder(e, $params) {\n if ($params.placeElm) {\n var _offset = $TreeDnDHelper.offset($params.placeElm);\n if (_offset.top <= e.pageY && e.pageY <= _offset.top + _offset.height &&\n _offset.left <= e.pageX && e.pageX <= _offset.left + _offset.width\n ) {\n return true;\n }\n }\n return false;\n }\n\n function _fnDragStart(e, $params) {\n if (!$params.hasTouch && (e.button === 2 || e.which === 3)) {\n // disable right click\n return;\n }\n\n if (e.uiTreeDragging || e.originalEvent && e.originalEvent.uiTreeDragging) { // event has already fired in other scope.\n return;\n }\n\n // the element which is clicked.\n var eventElm = angular.element(e.target),\n eventScope = eventElm.scope();\n if (!eventScope || !eventScope.$type) {\n return;\n }\n // if (eventScope.$type !== 'TreeDnDNode') { // Check if it is a node or a handle\n // return;\n // }\n\n if (eventScope.$type !== 'TreeDnDNodeHandle') { // If the node has a handle, then it should be clicked by the handle\n return;\n }\n\n var eventElmTagName = eventElm.prop('tagName').toLowerCase(),\n dragScope,\n _$scope = $params.$scope;\n if (eventElmTagName === 'input'\n || eventElmTagName === 'textarea'\n || eventElmTagName === 'button'\n || eventElmTagName === 'select') { // if it's a input or button, ignore it\n return;\n }\n // check if it or it's parents has a 'data-nodrag' attribute\n while (eventElm && eventElm[0] && eventElm[0] !== $params.element) {\n if ($TreeDnDHelper.nodrag(eventElm)) { // if the node mark as `nodrag`, DONOT drag it.\n return;\n }\n eventElm = eventElm.parent();\n }\n\n e.uiTreeDragging = true; // stop event bubbling\n if (e.originalEvent) {\n e.originalEvent.uiTreeDragging = true;\n }\n e.preventDefault();\n\n dragScope = eventScope.getScopeNode();\n\n $params.dragInfo = $TreeDnDHelper.dragInfo(dragScope);\n\n if (!_$scope.$callbacks.beforeDrag(dragScope, $params.dragInfo)) {\n return;\n }\n\n $params.firstMoving = true;\n _$scope.setDragging($params.dragInfo);\n\n var eventObj = $TreeDnDHelper.eventObj(e);\n $params.pos = $TreeDnDHelper.positionStarted(eventObj, dragScope.$element);\n\n if (dragScope.isTable) {\n $params.dragElm = angular.element($params.$window.document.createElement('table'))\n .addClass(_$scope.$class.tree)\n .addClass(_$scope.$class.drag)\n .addClass(_$scope.$tree_class);\n } else {\n $params.dragElm = angular.element($params.$window.document.createElement('ul'))\n .addClass(_$scope.$class.drag)\n .addClass('tree-dnd-nodes')\n .addClass(_$scope.$tree_class);\n }\n\n $params.dragElm.css(\n {\n 'width': $TreeDnDHelper.width(dragScope.$element) + 'px',\n 'z-index': 9995\n }\n );\n\n $params.offsetEdge = 0;\n var _width = $TreeDnDHelper.width(dragScope.$element),\n _scope = dragScope,\n _element = _scope.$element,\n _clone,\n _needCollapse = !!_$scope.enabledCollapse,\n _copied = false,\n _tbody,\n _frag;\n\n if (_scope.isTable) {\n $params.offsetEdge = $params.dragInfo.node.__level__ - 1;\n _tbody = angular.element(document.createElement('tbody'));\n _frag = angular.element(document.createDocumentFragment());\n\n _$scope.for_all_descendants(\n $params.dragInfo.node, function (_node, _parent) {\n _scope = _$scope.getScope(_node);\n _element = _scope && _scope.$element;\n if (_scope && _element) {\n if (!_copied) {\n _clone = _element.clone();\n\n $TreeDnDHelper.replaceIndent(\n _$scope,\n _clone,\n _node.__level__ - $params.offsetEdge,\n 'padding-left'\n );\n\n _frag.append(_clone);\n\n // skip all, just clone parent\n if (_needCollapse) {\n _copied = true;\n }\n\n // hide if have status Move;\n if (_$scope.enabledMove && _$scope.$class.hidden &&\n (!_parent || _node.__visible__ || _parent.__visible__ && _parent.__expanded__)) {\n _element.addClass(_$scope.$class.hidden);\n }\n }\n }\n // skip children of node not expand.\n return _copied || _node.__visible__ === false || _node.__expanded__ === false;\n\n }, null, !_needCollapse\n );\n _tbody.append(_frag);\n $params.dragElm.append(_tbody);\n } else {\n\n _clone = _element.clone();\n if (_needCollapse) {\n _clone[0].querySelector('[tree-dnd-nodes]').remove();\n }\n\n // hide if have status Move;\n $params.dragElm.append(_clone);\n if (_$scope.enabledMove && _$scope.$class.hidden) {\n _element.addClass(_$scope.$class.hidden);\n }\n }\n\n $params.dragElm.css(\n {\n 'left': eventObj.pageX - $params.pos.offsetX + _$scope.$callbacks.calsIndent(\n $params.offsetEdge + 1,\n true,\n true\n ) + 'px',\n 'top': eventObj.pageY - $params.pos.offsetY + 'px'\n }\n );\n // moving item with descendant\n $params.$document.find('body').append($params.dragElm);\n if (_$scope.$callbacks.droppable()) {\n $params.placeElm = _$scope.initPlace(dragScope.$element, $params.dragElm);\n\n if (dragScope.isTable) {\n $TreeDnDHelper.replaceIndent(_$scope, $params.placeElm, $params.dragInfo.node.__level__);\n }\n\n $params.placeElm.css('width', _width);\n }\n\n _$scope.showPlace();\n _$scope.targeting = true;\n\n if (_$scope.enabledStatus) {\n _$scope.refreshStatus();\n _$scope.setPositionStatus(e);\n }\n\n angular.element($params.$document).bind('touchend', $params.dragEndEvent);\n angular.element($params.$document).bind('touchcancel', $params.dragEndEvent);\n angular.element($params.$document).bind('touchmove', $params.dragMoveEvent);\n angular.element($params.$document).bind('mouseup', $params.dragEndEvent);\n angular.element($params.$document).bind('mousemove', $params.dragMoveEvent);\n angular.element($params.$document).bind('mouseleave', $params.dragCancelEvent);\n\n $params.document_height = Math.max(\n $params.body.scrollHeight,\n $params.body.offsetHeight,\n $params.html.clientHeight,\n $params.html.scrollHeight,\n $params.html.offsetHeight\n );\n\n $params.document_width = Math.max(\n $params.body.scrollWidth,\n $params.body.offsetWidth,\n $params.html.clientWidth,\n $params.html.scrollWidth,\n $params.html.offsetWidth\n );\n }\n\n function _fnDragMove(e, $params) {\n var _$scope = $params.$scope;\n if (!$params.dragStarted) {\n if (!$params.dragDelaying) {\n $params.dragStarted = true;\n _$scope.$safeApply(\n function () {\n _$scope.$callbacks.dragStart($params.dragInfo);\n }\n );\n }\n return;\n }\n\n if ($params.dragElm) {\n e.preventDefault();\n if ($params.$window.getSelection) {\n $params.$window.getSelection().removeAllRanges();\n } else if ($params.$window.document.selection) {\n $params.$window.document.selection.empty();\n }\n\n var eventObj = $TreeDnDHelper.eventObj(e),\n leftElmPos = eventObj.pageX - $params.pos.offsetX,\n topElmPos = eventObj.pageY - $params.pos.offsetY;\n\n //dragElm can't leave the screen on the left\n if (leftElmPos < 0) {\n leftElmPos = 0;\n }\n\n //dragElm can't leave the screen on the top\n if (topElmPos < 0) {\n topElmPos = 0;\n }\n\n //dragElm can't leave the screen on the bottom\n if (topElmPos + 10 > $params.document_height) {\n topElmPos = $params.document_height - 10;\n }\n\n //dragElm can't leave the screen on the right\n if (leftElmPos + 10 > $params.document_width) {\n leftElmPos = $params.document_width - 10;\n }\n\n $params.dragElm.css(\n {\n 'left': leftElmPos + _$scope.$callbacks.calsIndent(\n $params.offsetEdge + 1,\n true,\n true\n ) + 'px',\n 'top': topElmPos + 'px'\n }\n );\n\n if (_$scope.enabledStatus) {\n _$scope.setPositionStatus(e);\n }\n\n var top_scroll = window.pageYOffset || $params.$window.document.documentElement.scrollTop,\n bottom_scroll = top_scroll + (window.innerHeight || $params.$window.document.clientHeight || $params.$window.document.clientHeight);\n // to scroll down if cursor y-position is greater than the bottom position the vertical scroll\n if (bottom_scroll < eventObj.pageY && bottom_scroll <= $params.document_height) {\n window.scrollBy(0, 10);\n }\n // to scroll top if cursor y-position is less than the top position the vertical scroll\n if (top_scroll > eventObj.pageY) {\n window.scrollBy(0, -10);\n }\n\n $TreeDnDHelper.positionMoved(e, $params.pos, $params.firstMoving);\n\n if ($params.firstMoving) {\n $params.firstMoving = false;\n return;\n }\n // check if add it as a child node first\n\n var targetX = eventObj.pageX - $params.$window.document.body.scrollLeft,\n targetY = eventObj.pageY - (window.pageYOffset || $params.$window.document.documentElement.scrollTop),\n\n targetElm,\n targetScope,\n targetBefore,\n targetOffset,\n isChanged = true,\n isVeritcal = true,\n isEmpty,\n isSwapped,\n _scope,\n _target,\n _parent,\n _info = $params.dragInfo,\n _move = _info.move,\n _drag = _info.node,\n _drop = _info.drop,\n treeScope = _info.target,\n fnSwapTree,\n isHolder = _fnPlaceHolder(e, $params);\n\n if (!isHolder) {\n /* when using elementFromPoint() inside an iframe, you have to call\n elementFromPoint() twice to make sure IE8 returns the correct value\n $params.$window.document.elementFromPoint(targetX, targetY);*/\n\n targetElm = angular.element(\n $params.$window.document.elementFromPoint(\n targetX,\n targetY\n )\n );\n\n targetScope = targetElm.scope();\n if (!targetScope || !targetScope.$callbacks || !targetScope.$callbacks.droppable()) {\n // Not allowed Drop Item\n return;\n }\n\n fnSwapTree = function () {\n treeScope = targetScope.getScopeTree();\n _target = _info.target;\n\n if (_info.target !== treeScope) {\n // Replace by place-holder new\n _target.hidePlace();\n _target.targeting = false;\n treeScope.targeting = true;\n\n _info.target = treeScope;\n $params.placeElm = treeScope.initPlace(targetScope.$element, $params.dragElm);\n\n _target = null;\n isSwapped = true;\n }\n return true;\n };\n\n if (angular.isFunction(targetScope.getScopeNode)) {\n targetScope = targetScope.getScopeNode();\n if (!fnSwapTree()) {\n return;\n }\n } else {\n if (targetScope.$type === 'TreeDnDNodes' || targetScope.$type === 'TreeDnD') {\n if (targetScope.tree_nodes) {\n if (targetScope.tree_nodes.length === 0) {\n if (!fnSwapTree()) {\n return;\n }\n // Empty\n isEmpty = true;\n }\n } else {\n return;\n }\n } else {\n return;\n }\n }\n }\n\n if ($params.pos.dirAx && !isSwapped || isHolder) {\n isVeritcal = false;\n targetScope = _info.scope;\n }\n\n if (!targetScope.$element && !targetScope) {\n return;\n }\n\n if (isEmpty) {\n _move.parent = null;\n _move.pos = 0;\n\n _drop = null;\n } else {\n // move vertical\n if (isVeritcal) {\n targetElm = targetScope.$element; // Get the element of tree-dnd-node\n if (angular.isUndefinedOrNull(targetElm)) {\n return;\n }\n targetOffset = $TreeDnDHelper.offset(targetElm);\n\n if (targetScope.horizontal && !targetScope.isTable) {\n targetBefore = eventObj.pageX < targetOffset.left + $TreeDnDHelper.width(targetElm) / 2;\n } else {\n if (targetScope.isTable) {\n targetBefore = eventObj.pageY < targetOffset.top + $TreeDnDHelper.height(targetElm) / 2;\n } else {\n var _height = $TreeDnDHelper.height(targetElm);\n\n if (targetScope.getElementChilds()) {\n _height -= -$TreeDnDHelper.height(targetScope.getElementChilds());\n }\n\n if (eventObj.pageY > targetOffset.top + _height) {\n return;\n }\n\n targetBefore = eventObj.pageY < targetOffset.top + _height / 2;\n }\n }\n\n if (!angular.isFunction(targetScope.getData)) {\n return;\n }\n\n _target = targetScope.getData();\n _parent = targetScope.getNode(_target.__parent_real__);\n\n if (targetBefore) {\n var _prev = targetScope.getPrevSibling(_target);\n\n _move.parent = _parent;\n _move.pos = angular.isDefined(_prev) ? _prev.__index__ + 1 : 0;\n\n _drop = _prev;\n } else {\n if (_target.__expanded__ && !(_target.__children__.length === 1 && _target.__index_real__ === _drag.__parent_real__)) {\n _move.parent = _target;\n _move.pos = 0;\n\n _drop = null;\n } else {\n _move.parent = _parent;\n _move.pos = _target.__index__ + 1;\n\n _drop = _target;\n }\n }\n } else {\n // move horizontal\n if ($params.pos.dirAx && $params.pos.distAxX >= treeScope.dragBorder) {\n $params.pos.distAxX = 0;\n // increase horizontal level if previous sibling exists and is not collapsed\n if ($params.pos.distX > 0) {\n _parent = _drop;\n if (!_parent) {\n if (_move.pos - 1 >= 0) {\n _parent = _move.parent.__children__[_move.pos - 1];\n } else {\n return;\n }\n }\n\n if (_info.drag === _info.target && _parent === _drag && _$scope.enabledMove) {\n _parent = treeScope.getPrevSibling(_parent);\n }\n\n if (_parent && _parent.__visible__) {\n var _len = _parent.__children__.length;\n\n _move.parent = _parent;\n _move.pos = _len;\n\n if (_len > 0) {\n _drop = _parent.__children__[_len - 1];\n } else {\n _drop = null;\n }\n } else {\n // Not changed\n return;\n }\n } else if ($params.pos.distX < 0) {\n _target = _move.parent;\n if (_target &&\n (_target.__children__.length === 0 ||\n _target.__children__.length - 1 < _move.pos ||\n _info.drag === _info.target &&\n _target.__index_real__ === _drag.__parent_real__ &&\n _target.__children__.length - 1 === _drag.__index__ && _$scope.enabledMove)\n ) {\n _parent = treeScope.getNode(_target.__parent_real__);\n\n _move.parent = _parent;\n _move.pos = _target.__index__ + 1;\n\n _drop = _target;\n } else {\n // Not changed\n return;\n }\n } else {\n return;\n }\n } else {\n // limited\n return;\n }\n }\n }\n\n if (_info.drag === _info.target &&\n _move.parent &&\n _drag.__parent_real__ === _move.parent.__index_real__ &&\n _drag.__index__ === _move.pos\n ) {\n isChanged = false;\n }\n\n if (treeScope.$callbacks.accept(_info, _move, isChanged)) {\n _info.move = _move;\n _info.drop = _drop;\n _info.changed = isChanged;\n _info.scope = targetScope;\n\n if (targetScope.isTable) {\n $TreeDnDHelper.replaceIndent(\n treeScope,\n $params.placeElm,\n angular.isUndefinedOrNull(_move.parent) ? 1 : _move.parent.__level__ + 1\n );\n\n if (_drop) {\n _parent = (_move.parent ? _move.parent.__children__ : null) || _info.target.treeData;\n\n if (_drop.__index__ < _parent.length - 1) {\n // Find fast\n _drop = _parent[_drop.__index__ + 1];\n _scope = _info.target.getScope(_drop);\n _scope.$element[0].parentNode.insertBefore(\n $params.placeElm[0],\n _scope.$element[0]\n );\n } else {\n _target = _info.target.getLastDescendant(_drop);\n _scope = _info.target.getScope(_target);\n _scope.$element.after($params.placeElm);\n }\n } else {\n _scope = _info.target.getScope(_move.parent);\n if (_scope) {\n if (_move.parent) {\n _scope.$element.after($params.placeElm);\n\n } else {\n _scope.getElementChilds().prepend($params.placeElm);\n }\n }\n }\n } else {\n _scope = _info.target.getScope(_drop || _move.parent);\n if (_drop) {\n _scope.$element.after($params.placeElm);\n } else {\n _scope.getElementChilds().prepend($params.placeElm);\n }\n }\n\n treeScope.showPlace();\n\n _$scope.$safeApply(\n function () {\n _$scope.$callbacks.dragMove(_info);\n }\n );\n }\n\n }\n }\n\n function _fnDragEnd(e, $params) {\n e.preventDefault();\n if ($params.dragElm) {\n var _passed = false,\n _$scope = $params.$scope,\n _scope = _$scope.getScope($params.dragInfo.node),\n _element = _scope.$element;\n\n _$scope.$safeApply(\n function () {\n _passed = _$scope.$callbacks.beforeDrop($params.dragInfo);\n }\n );\n\n // rollback all\n if (_scope.isTable) {\n _$scope.for_all_descendants(\n $params.dragInfo.node, function (_node, _parent) {\n _scope = _$scope.getScope(_node);\n _element = _scope && _scope.$element;\n if (_scope && _element && (!_parent && _node.__visible__ || _parent.__expanded__)) {\n if (_$scope.$class.hidden) {\n _element.removeClass(_$scope.$class.hidden);\n }\n }\n return _node.__visible__ === false || _node.__expanded__ === false\n }, null, true\n );\n } else {\n if (_$scope.$class.hidden) {\n _element.removeClass(_$scope.$class.hidden);\n }\n }\n\n $params.dragElm.remove();\n $params.dragElm = null;\n\n if (_$scope.enabledStatus) {\n _$scope.hideStatus();\n }\n\n if (_$scope.$$apply) {\n _$scope.$safeApply(\n function () {\n var _status = _$scope.$callbacks.dropped(\n $params.dragInfo,\n _passed\n );\n\n _$scope.$callbacks.dragStop($params.dragInfo, _status);\n clearData();\n }\n );\n } else {\n _fnBindDrag($params);\n _$scope.$safeApply(\n function () {\n _$scope.$callbacks.dragStop($params.dragInfo, false);\n clearData();\n }\n );\n }\n\n }\n\n function clearData() {\n $params.dragInfo.target.hidePlace();\n $params.dragInfo.target.targeting = false;\n\n $params.dragInfo = null;\n _$scope.$$apply = false;\n _$scope.setDragging(null);\n }\n\n angular.element($params.$document).unbind('touchend', $params.dragEndEvent); // Mobile\n angular.element($params.$document).unbind('touchcancel', $params.dragEndEvent); // Mobile\n angular.element($params.$document).unbind('touchmove', $params.dragMoveEvent); // Mobile\n angular.element($params.$document).unbind('mouseup', $params.dragEndEvent);\n angular.element($params.$document).unbind('mousemove', $params.dragMoveEvent);\n angular.element($params.$window.document.body).unbind('mouseleave', $params.dragCancelEvent);\n }\n\n function _fnDragStartEvent(e, $params) {\n if ($params.$scope.$callbacks.draggable()) {\n _fnDragStart(e, $params);\n }\n }\n\n function _fnBindDrag($params) {\n $params.element.bind(\n 'touchstart mousedown', function (e) {\n $params.dragDelaying = true;\n $params.dragStarted = false;\n _fnDragStartEvent(e, $params);\n $params.dragTimer = $timeout(\n function () {\n $params.dragDelaying = false;\n }, $params.$scope.dragDelay\n );\n }\n );\n\n $params.element.bind(\n 'touchend touchcancel mouseup', function () {\n $timeout.cancel($params.dragTimer);\n }\n );\n }\n\n function _fnKeydownHandler(e, $params) {\n var _$scope = $params.$scope;\n if (e.keyCode === 27) {\n if (_$scope.enabledStatus) {\n _$scope.hideStatus();\n }\n\n _$scope.$$apply = false;\n _fnDragEnd(e, $params);\n } else {\n if (_$scope.enabledHotkey && e.shiftKey) {\n _$scope.enableMove(true);\n if (_$scope.enabledStatus) {\n _$scope.refreshStatus();\n }\n\n if (!$params.dragInfo) {\n return;\n }\n\n var _scope = _$scope.getScope($params.dragInfo.node),\n _element = _scope.$element;\n\n if (_scope.isTable) {\n _$scope.for_all_descendants(\n $params.dragInfo.node, function (_node, _parent) {\n _scope = _$scope.getScope(_node);\n _element = _scope && _scope.$element;\n if (_scope && _element && (!_parent && _node.__visible__ || _parent.__expanded__)) {\n if (_$scope.$class.hidden) {\n _element.addClass(_$scope.$class.hidden);\n }\n }\n return _node.__visible__ === false || _node.__expanded__ === false\n\n }, null, true\n );\n } else {\n if (_$scope.$class.hidden) {\n _element.addClass(_$scope.$class.hidden);\n }\n }\n }\n }\n }\n\n function _fnKeyupHandler(e, $params) {\n var _$scope = $params.$scope;\n if (_$scope.enabledHotkey && !e.shiftKey) {\n _$scope.enableMove(false);\n\n if (_$scope.enabledStatus) {\n _$scope.refreshStatus();\n }\n\n if (!$params.dragInfo) {\n return;\n }\n\n var _scope = _$scope.getScope($params.dragInfo.node),\n _element = _scope.$element;\n\n if (_scope.isTable) {\n _$scope.for_all_descendants(\n $params.dragInfo.node, function (_node, _parent) {\n _scope = _$scope.getScope(_node);\n _element = _scope && _scope.$element;\n if (_scope && _element && (!_parent && _node.__visible__ || _parent.__expanded__)) {\n if (_$scope.$class.hidden) {\n _element.removeClass(_$scope.$class.hidden);\n }\n }\n return _node.__visible__ === false || _node.__expanded__ === false\n }, null, true\n );\n } else {\n if (_$scope.$class.hidden) {\n _element.removeClass(_$scope.$class.hidden);\n }\n }\n }\n }\n\n function _$init(scope, element, $window, $document) {\n var $params = {\n hasTouch: 'ontouchstart' in window,\n firstMoving: null,\n dragInfo: null,\n pos: null,\n placeElm: null,\n dragElm: null,\n dragDelaying: true,\n dragStarted: false,\n dragTimer: null,\n body: document.body,\n html: document.documentElement,\n document_height: null,\n document_width: null,\n offsetEdge: null,\n $scope: scope,\n $window: $window,\n $document: $document,\n element: element,\n bindDrag: function () {\n _fnBindDrag($params);\n },\n dragEnd: function (e) {\n _fnDragEnd(e, $params);\n },\n dragMoveEvent: function (e) {\n _fnDragMove(e, $params);\n },\n dragEndEvent: function (e) {\n scope.$$apply = true;\n _fnDragEnd(e, $params);\n },\n dragCancelEvent: function (e) {\n _fnDragEnd(e, $params);\n }\n },\n keydownHandler = function (e) {\n return _fnKeydownHandler(e, $params);\n },\n keyupHandler = function (e) {\n return _fnKeyupHandler(e, $params);\n };\n\n scope.dragEnd = function (e) {\n $params.dragEnd(e);\n };\n\n $params.bindDrag();\n\n angular.element($window.document.body).bind('keydown', keydownHandler);\n angular.element($window.document.body).bind('keyup', keyupHandler);\n //unbind handler that retains scope\n scope.$on(\n '$destroy', function () {\n angular.element($window.document.body).unbind('keydown', keydownHandler);\n angular.element($window.document.body).unbind('keyup', keyupHandler);\n if (scope.statusElm) {\n scope.statusElm.remove();\n }\n\n if (scope.placeElm) {\n scope.placeElm.remove();\n }\n }\n );\n }\n\n return _$init;\n }\n ]);\n\nangular.module('ntt.TreeDnD')\n .factory('$TreeDnDControl', function () {\n var _target, _parent,\n i, len;\n\n function fnSetCollapse(node) {\n node.__expanded__ = false;\n }\n\n function fnSetExpand(node) {\n node.__expanded__ = true;\n }\n\n function _$init(scope) {\n var n, tree = {\n selected_node: null,\n for_all_descendants: scope.for_all_descendants,\n select_node: function (node) {\n if (!node) {\n if (tree.selected_node) {\n delete tree.selected_node.__selected__;\n }\n tree.selected_node = null;\n return null;\n }\n\n if (node !== tree.selected_node) {\n if (tree.selected_node) {\n delete tree.selected_node.__selected__;\n }\n node.__selected__ = true;\n tree.selected_node = node;\n tree.expand_all_parents(node);\n if (angular.isFunction(tree.on_select)) {\n tree.on_select(node);\n }\n }\n\n return node;\n },\n deselect_node: function () {\n _target = null;\n if (tree.selected_node) {\n delete tree.selected_node.__selected__;\n _target = tree.selected_node;\n tree.selected_node = null;\n }\n return _target;\n },\n get_parent: function (node) {\n node = node || tree.selected_node;\n\n if (node && node.__parent_real__ !== null) {\n return scope.tree_nodes[node.__parent_real__];\n }\n return null;\n },\n for_all_ancestors: function (node, fn) {\n _parent = tree.get_parent(node);\n if (_parent) {\n if (fn(_parent)) {\n return false;\n }\n\n return tree.for_all_ancestors(_parent, fn);\n }\n return true;\n },\n expand_all_parents: function (node) {\n node = node || tree.selected_node;\n\n if (angular.isObject(node)) {\n tree.for_all_ancestors(node, fnSetExpand);\n }\n },\n collapse_all_parents: function (node) {\n node = node || tree.selected_node;\n if (angular.isObject(node)) {\n tree.for_all_ancestors(node, fnSetCollapse);\n }\n },\n\n reload_data: function () {\n return scope.reload_data();\n },\n add_node: function (parent, new_node, index) {\n if (typeof index !== 'number') {\n if (parent) {\n parent.__children__.push(new_node);\n parent.__expanded__ = true;\n } else {\n scope.treeData.push(new_node);\n }\n } else {\n if (parent) {\n parent.__children__.splice(index, 0, new_node);\n parent.__expanded__ = true;\n } else {\n scope.treeData.splice(index, 0, new_node);\n }\n }\n return new_node;\n },\n add_node_root: function (new_node) {\n tree.add_node(null, new_node);\n return new_node;\n },\n expand_all: function () {\n len = scope.treeData.length;\n for (i = 0; i < len; i++) {\n tree.for_all_descendants(scope.treeData[i], fnSetExpand);\n }\n },\n collapse_all: function () {\n len = scope.treeData.length;\n for (i = 0; i < len; i++) {\n tree.for_all_descendants(scope.treeData[i], fnSetCollapse);\n }\n },\n remove_node: function (node) {\n node = node || tree.selected_node;\n\n if (angular.isObject(node)) {\n if (node.__parent_real__ !== null) {\n _parent = tree.get_parent(node).__children__;\n } else {\n _parent = scope.treeData;\n }\n\n _parent.splice(node.__index__, 1);\n\n tree.reload_data();\n\n if (tree.selected_node === node) {\n tree.selected_node = null;\n }\n }\n },\n expand_node: function (node) {\n node = node || tree.selected_node;\n\n if (angular.isObject(node)) {\n node.__expanded__ = true;\n return node;\n }\n },\n collapse_node: function (node) {\n node = node || tree.selected_node;\n\n if (angular.isObject(node)) {\n node.__expanded__ = false;\n return node;\n }\n },\n get_selected_node: function () {\n return tree.selected_node;\n },\n get_first_node: function () {\n len = scope.treeData.length;\n if (len > 0) {\n return scope.treeData[0];\n }\n\n return null;\n },\n get_children: function (node) {\n node = node || tree.selected_node;\n\n return node.__children__;\n },\n get_siblings: function (node) {\n node = node || tree.selected_node;\n if (angular.isObject(node)) {\n _parent = tree.get_parent(node);\n if (_parent) {\n _target = _parent.__children__;\n } else {\n _target = scope.treeData;\n }\n return _target;\n }\n },\n get_next_sibling: function (node) {\n node = node || tree.selected_node;\n if (angular.isObject(node)) {\n _target = tree.get_siblings(node);\n n = _target.length;\n if (node.__index__ < n) {\n return _target[node.__index__ + 1];\n }\n }\n },\n get_prev_sibling: function (node) {\n node = node || tree.selected_node;\n _target = tree.get_siblings(node);\n if (node.__index__ > 0) {\n return _target[node.__index__ - 1];\n }\n },\n get_first_child: function (node) {\n node = node || tree.selected_node;\n if (angular.isObject(node)) {\n _target = node.__children__;\n if (_target && _target.length > 0) {\n return node.__children__[0];\n }\n }\n return null;\n },\n get_closest_ancestor_next_sibling: function (node) {\n node = node || tree.selected_node;\n _target = tree.get_next_sibling(node);\n if (_target) {\n return _target;\n }\n\n _parent = tree.get_parent(node);\n if (_parent) {\n return tree.get_closest_ancestor_next_sibling(_parent);\n }\n\n return null;\n },\n get_next_node: function (node) {\n node = node || tree.selected_node;\n\n if (angular.isObject(node)) {\n _target = tree.get_first_child(node);\n if (_target) {\n return _target;\n } else {\n return tree.get_closest_ancestor_next_sibling(node);\n }\n }\n },\n get_prev_node: function (node) {\n node = node || tree.selected_node;\n\n if (angular.isObject(node)) {\n _target = tree.get_prev_sibling(node);\n if (_target) {\n return tree.get_last_descendant(_target);\n }\n\n _parent = tree.get_parent(node);\n return _parent;\n }\n },\n get_last_descendant: scope.getLastDescendant,\n select_parent_node: function (node) {\n node = node || tree.selected_node;\n\n if (angular.isObject(node)) {\n _parent = tree.get_parent(node);\n if (_parent) {\n return tree.select_node(_parent);\n }\n }\n },\n select_first_node: function () {\n var firstNode = tree.get_first_node();\n return tree.select_node(firstNode);\n },\n select_next_sibling: function (node) {\n node = node || tree.selected_node;\n\n if (angular.isObject(node)) {\n _target = tree.get_next_sibling(node);\n if (_target) {\n return tree.select_node(_target);\n }\n }\n },\n select_prev_sibling: function (node) {\n node = node || tree.selected_node;\n\n if (angular.isObject(node)) {\n _target = tree.get_prev_sibling(node);\n if (_target) {\n return tree.select_node(_target);\n }\n }\n },\n select_next_node: function (node) {\n node = node || tree.selected_node;\n\n if (angular.isObject(node)) {\n _target = tree.get_next_node(node);\n if (_target) {\n return tree.select_node(_target);\n }\n }\n },\n select_prev_node: function (node) {\n node = node || tree.selected_node;\n\n if (angular.isObject(node)) {\n _target = tree.get_prev_node(node);\n if (_target) {\n return tree.select_node(_target);\n }\n }\n }\n };\n angular.extend(scope.tree, tree);\n return scope.tree;\n }\n\n return _$init;\n });\n\nangular.module('template/TreeDnD/TreeDnD.html', []).run(\n ['$templateCache', function ($templateCache) {\n $templateCache.put(\n 'template/TreeDnD/TreeDnD.html',\n ['',\n ' ',\n ' ',\n ' ',\n ' ',\n ' ',\n ' ',\n ' ',\n ' ',\n ' ',\n ' ',\n ' ',\n '
',\n ' {{expandingProperty.displayName || expandingProperty.field || expandingProperty}}',\n ' <\\/th>',\n ' ',\n ' {{col.displayName || col.field}}',\n '
',\n ' ',\n ' ',\n ' ',\n ' {{node[expandingProperty.field] || node[expandingProperty]}}',\n ' ',\n ' {{node[col.field]}}',\n '
'].join('\\n')\n );\n\n $templateCache.put(\n 'template/TreeDnD/TreeDnDStatusCopy.html',\n ''\n );\n\n $templateCache.put(\n 'template/TreeDnD/TreeDnDStatusMove.html',\n ''\n );\n }]\n);\n\n function isUndefinedOrNull(val) {\n return angular.isUndefined(val) || val === null;\n }\n\n function isDefined(val) {\n return !(angular.isUndefined(val) || val === null);\n }\n})();"]} \ No newline at end of file +{"version":3,"file":"ng-tree-dnd.min.js","sources":["ng-tree-dnd.js"],"sourcesContent":["/**\n * The MIT License (MIT)\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n * @preserve\n */\n\n/**\n * Implementing TreeDnD & Event DrapnDrop (allow drag multi tree-table include all type: table, ol, ul)\n * Demo: http://thienhung1989.github.io/angular-tree-dnd\n * Github: https://github.com/thienhung1989/angular-tree-dnd\n * @version 3.0.7\n * @preserve\n * (c) 2015 Nguyuễn Thiện Hùng - \n */\n(function () {\n 'use strict';\n angular.isUndefinedOrNull = isUndefinedOrNull;\n\n angular.isDefined = isDefined;\n\n angular.module('ntt.TreeDnD', ['template/TreeDnD/TreeDnD.html'])\n .constant('$TreeDnDClass', {\n tree: 'tree-dnd',\n empty: 'tree-dnd-empty',\n hidden: 'tree-dnd-hidden',\n node: 'tree-dnd-node',\n nodes: 'tree-dnd-nodes',\n handle: 'tree-dnd-handle',\n place: 'tree-dnd-placeholder',\n drag: 'tree-dnd-drag',\n status: 'tree-dnd-status',\n icon: {\n '1': 'glyphicon glyphicon-minus',\n '0': 'glyphicon glyphicon-plus',\n '-1': 'glyphicon glyphicon-file'\n }\n });angular.module('ntt.TreeDnD')\n .directive('compile', [\n '$compile',\n function ($compile) {\n return {\n restrict: 'A',\n link: function (scope, element, attrs) {\n // Store the deregistration function to prevent memory leaks\n var unwatchCompile = scope.$watch(\n attrs.compile, function (new_val) {\n if (new_val) {\n if (angular.isFunction(element.empty)) {\n element.empty();\n } else {\n element.html('');\n }\n\n element.append($compile(new_val)(scope));\n }\n }\n );\n\n // Clean up watch on scope destroy\n scope.$on('$destroy', function () {\n if (unwatchCompile) {\n unwatchCompile();\n unwatchCompile = null;\n }\n });\n }\n };\n }]\n )\n .directive('compileReplace', [\n '$compile',\n function ($compile) {\n return {\n restrict: 'A',\n link: function (scope, element, attrs) {\n // Store the deregistration function to prevent memory leaks\n var unwatchCompileReplace = scope.$watch(\n attrs.compileReplace, function (new_val) {\n if (new_val) {\n element.replaceWith($compile(new_val)(scope));\n }\n }\n );\n\n // Clean up watch on scope destroy\n scope.$on('$destroy', function () {\n if (unwatchCompileReplace) {\n unwatchCompileReplace();\n unwatchCompileReplace = null;\n }\n });\n }\n };\n }]\n );\n\n\nangular.module('ntt.TreeDnD')\r .directive('treeDndNodeHandle', function () {\r return {\r restrict: 'A',\r scope: true,\r link: function (scope, element/*, attrs*/) {\r scope.$type = 'TreeDnDNodeHandle';\r if (scope.$class.handle) {\r element.addClass(scope.$class.handle);\r }\r }\r };\r });\n\nangular.module('ntt.TreeDnD')\n .directive('treeDndNode', [\n '$TreeDnDViewport',\n function ($TreeDnDViewport) {\n return {\n restrict: 'A',\n replace: true,\n link: fnLink\n };\n\n function fnLink(scope, element, attrs) {\n\n scope.$node_class = '';\n\n if (scope.$class.node) {\n element.addClass(scope.$class.node);\n scope.$node_class = scope.$class.node;\n }\n var enabledDnD = typeof scope.dragEnabled === 'boolean' || typeof scope.dropEnabled === 'boolean',\n keyNode = attrs.treeDndNode,\n first = true,\n childsElem;\n $TreeDnDViewport.add(scope, element);\n\n if (enabledDnD) {\n scope.$type = 'TreeDnDNode';\n\n scope.getData = function () {\n return scope[keyNode];\n };\n }\n\n scope.$element = element;\n scope[keyNode].__inited__ = true;\n\n scope.getElementChilds = function () {\n return angular.element(element[0].querySelector('[tree-dnd-nodes]'));\n };\n\n scope.setScope(scope, scope[keyNode]);\n\n scope.getScopeNode = function () {\n return scope;\n };\n\n var objprops = [],\n objexpr,\n i, keyO = Object.keys(scope[keyNode]),\n lenO = keyO.length,\n hashKey = scope[keyNode].__hashKey__,\n skipAttr = [\n '__visible__',\n '__children__',\n '__level__',\n '__index__',\n '__index_real__',\n\n '__parent__',\n '__parent_real__',\n '__dept__',\n '__icon__',\n '__icon_class__'\n ],\n keepAttr = [\n '__expanded__'\n ],\n lenKeep = keepAttr.length;\n\n // skip __visible__\n for (i = 0; i < lenO + lenKeep; i++) {\n if (i < lenO) {\n if (skipAttr.indexOf(keyO[i]) === -1) {\n objprops.push(keyNode + '.' + keyO[i]);\n }\n } else {\n if (keyO.indexOf(keepAttr[i - lenO]) === -1) {\n objprops.push(keyNode + '.' + keepAttr[i - lenO]);\n }\n }\n }\n\n objexpr = '[' + objprops.join(',') + ']';\n\n var unwatchNode = scope.$watch(objexpr, fnWatchNode, true);\n\n scope.$on('$destroy', function () {\n // Deregister the watch first\n if (unwatchNode) {\n unwatchNode();\n unwatchNode = null;\n }\n\n // Remove from scope cache\n scope.deleteScope(scope, scope[keyNode]);\n\n // Remove from viewport\n $TreeDnDViewport.remove(scope, element);\n\n // Clear the __inited__ flag on the node\n if (scope[keyNode]) {\n scope[keyNode].__inited__ = false;\n }\n\n // Clear element reference to allow DOM garbage collection\n scope.$element = null;\n\n // Clear cached child element reference\n childsElem = null;\n\n // Clear function references that may hold closures\n scope.getData = null;\n scope.getElementChilds = null;\n scope.getScopeNode = null;\n });\n\n function fnWatchNode(newVal, oldVal, scope) {\n\n var nodeOf = scope[keyNode],\n _icon;\n\n if (first) {\n _icon = nodeOf.__icon__;\n nodeOf.__icon_class__ = scope.$class.icon[_icon];\n } else {\n\n var parentReal = nodeOf.__parent_real__,\n parentNode = scope.tree_nodes[parentReal] || null,\n _childs = nodeOf.__children__,\n _len = _childs.length,\n _hasChilds = _len > 0 || nodeOf.__has_children__ === true || nodeOf.__lazy__ === true,\n _i;\n\n if (!nodeOf.__inited__) {\n nodeOf.__inited__ = true;\n }\n\n if (nodeOf.__hashKey__ !== hashKey) {\n // clear scope in $globals\n scope.deleteScope(scope, nodeOf);\n\n // add new scope into $globals\n scope.setScope(scope, nodeOf);\n hashKey = nodeOf.__hashKey__;\n }\n\n if (parentNode && (!parentNode.__expanded__ || !parentNode.__visible__)) {\n element.addClass(scope.$class.hidden);\n nodeOf.__visible__ = false;\n } else {\n element.removeClass(scope.$class.hidden);\n nodeOf.__visible__ = true;\n }\n\n if (!_hasChilds) {\n _icon = -1;\n } else {\n if (nodeOf.__expanded__) {\n _icon = 1;\n } else {\n _icon = 0;\n }\n }\n\n nodeOf.__icon__ = _icon;\n nodeOf.__icon_class__ = scope.$class.icon[_icon];\n\n if (scope.isTable) {\n for (_i = 0; _i < _len; _i++) {\n scope.for_all_descendants(_childs[_i], scope.hiddenChild, nodeOf, true);\n }\n } else {\n if (!childsElem) {\n childsElem = scope.getElementChilds();\n }\n\n if (nodeOf.__expanded__) {\n childsElem.removeClass(scope.$class.hidden);\n } else {\n childsElem.addClass(scope.$class.hidden);\n }\n }\n\n }\n\n first = false;\n\n }\n }\n }]\n );\n\n\nangular.module('ntt.TreeDnD')\n .directive('treeDndNodes', function () {\n return {\n restrict: 'A',\n replace: true,\n link: function (scope, element/*, attrs*/) {\n scope.$type = 'TreeDnDNodes';\n\n if (scope.$class.nodes) {\n element.addClass(scope.$class.nodes);\n scope.$nodes_class = scope.$class.nodes;\n } else {\n scope.$nodes_class = '';\n }\n }\n };\n });\n\nangular.module('ntt.TreeDnD')\n .directive(\n 'treeDnd', fnInitTreeDnD);\n\nfnInitTreeDnD.$inject = [\n '$timeout', '$http', '$compile', '$parse', '$window', '$document', '$templateCache', '$q',\n '$TreeDnDTemplate', '$TreeDnDClass', '$TreeDnDHelper', '$TreeDnDPlugin', '$TreeDnDViewport'\n];\n\nfunction fnInitTreeDnD($timeout, $http, $compile, $parse, $window, $document, $templateCache, $q,\n $TreeDnDTemplate, $TreeDnDClass, $TreeDnDHelper, $TreeDnDPlugin, $TreeDnDViewport) {\n return {\n restrict: 'E',\n scope: true,\n replace: true,\n controller: ['$scope', '$element', '$attrs', fnController],\n compile: fnCompile\n };\n\n function fnController($scope, $element, $attrs) {\n $scope.indent = 20;\n $scope.indent_plus = 15;\n $scope.indent_unit = 'px';\n $scope.$tree_class = 'table';\n $scope.primary_key = '__uid__';\n\n $scope.$type = 'TreeDnD';\n // $scope.enabledFilter = null;\n $scope.colDefinitions = [];\n $scope.$globals = {};\n $scope.$class = {};\n\n $scope.treeData = [];\n $scope.tree_nodes = [];\n\n $scope.$class = angular.copy($TreeDnDClass);\n angular.extend(\n $scope.$class.icon, {\n '1': $attrs.iconExpand || 'glyphicon glyphicon-minus',\n '0': $attrs.iconCollapse || 'glyphicon glyphicon-plus',\n '-1': $attrs.iconLeaf || 'glyphicon glyphicon-file'\n }\n );\n\n $scope.for_all_descendants = function (node, fn, parent, checkSibling) {\n if (angular.isFunction(fn)) {\n var _i, _len, _nodes;\n\n if (fn(node, parent)) {\n // have error or need ignore children\n return false;\n }\n _nodes = node.__children__;\n _len = _nodes ? _nodes.length : 0;\n for (_i = 0; _i < _len; _i++) {\n if (!$scope.for_all_descendants(_nodes[_i], fn, node) && !checkSibling) {\n // skip sibling of node checking\n return false;\n }\n }\n }\n // succeed then continue\n return true;\n };\n\n $scope.getLastDescendant = function (node) {\n var last_child, n;\n if (!node) {\n node = $scope.tree ? $scope.tree.selected_node : false;\n }\n if (node === false) {\n return false;\n }\n n = node.__children__.length;\n if (n === 0) {\n return node;\n } else {\n last_child = node.__children__[n - 1];\n return $scope.getLastDescendant(last_child);\n }\n };\n\n $scope.getElementChilds = function () {\n return angular.element($element[0].querySelector('[tree-dnd-nodes]'));\n };\n\n $scope.onClick = function (node) {\n if (angular.isDefined($scope.tree) && angular.isFunction($scope.tree.on_click)) {\n // We want to detach from Angular's digest cycle so we can\n // independently measure the time for one cycle.\n setTimeout(\n function () {\n $scope.tree.on_click(node);\n }, 0\n );\n }\n };\n\n $scope.onSelect = function (node) {\n if (angular.isDefined($scope.tree)) {\n if (node !== $scope.tree.selected_node) {\n $scope.tree.select_node(node);\n }\n\n if (angular.isFunction($scope.tree.on_select)) {\n setTimeout(\n function () {\n $scope.tree.on_select(node);\n }, 0\n );\n }\n }\n };\n\n var passedExpand, _clone;\n $scope.toggleExpand = function (node, fnCallback) {\n passedExpand = true;\n if (angular.isFunction(fnCallback) && !fnCallback(node)) {\n passedExpand = false;\n } else if (angular.isFunction($scope.$callbacks.expand) && !$scope.$callbacks.expand(node)) {\n passedExpand = false;\n }\n\n if (passedExpand) {\n if (node.__expanded__) {\n node.__expanded__ = false;\n return;\n }\n\n if (node.__children__.length > 0) {\n node.__expanded__ = true;\n return;\n }\n\n if (nodeHasChildren(node) && angular.isFunction($scope.$callbacks.loadChildren)) {\n $scope.loadChildren(node);\n }\n }\n };\n\n\n var _fnGetHash = function (node) {\n return '#' + node.__parent__ + '#' + node[$scope.primary_key];\n },\n _fnSetHash = function (node) {\n var _hashKey = _fnGetHash(node);\n if (angular.isUndefinedOrNull(node.__hashKey__) || node.__hashKey__ !== _hashKey) {\n node.__hashKey__ = _hashKey;\n }\n return node;\n };\n $scope.getHash = _fnGetHash;\n $scope.$callbacks = {\n getHash: _fnGetHash,\n setHash: _fnSetHash,\n for_all_descendants: $scope.for_all_descendants,\n /*expand: function (node) {\n return true;\n },*/\n accept: function (/*dragInfo, moveTo, isChanged*/) {\n return $scope.dropEnabled === true;\n },\n calsIndent: function (level, skipUnit, skipEdge) {\n var unit = 0,\n edge = skipEdge ? 0 : $scope.indent_plus;\n if (!skipUnit) {\n unit = $scope.indent_unit ? $scope.indent_unit : 'px';\n }\n\n if (level - 1 < 1) {\n return edge + unit;\n } else {\n return $scope.indent * (level - 1) + edge + unit;\n }\n },\n droppable: function () {\n return $scope.dropEnabled === true;\n },\n draggable: function () {\n return $scope.dragEnabled === true;\n },\n beforeDrop: function (/*event*/) {\n return true;\n },\n changeKey: function (node) {\n var _key = node.__uid__;\n node.__uid__ = Math.random();\n if (node.__selected__) {\n delete node.__selected__;\n }\n\n if ($scope.primary_key !== '__uid__') {\n _key = '' + node[$scope.primary_key];\n _key = _key.replace(/_#.+$/g, '') + '_#' + node.__uid__;\n\n node[$scope.primary_key] = _key;\n }\n // delete(node.__hashKey__);\n },\n clone: function (node/*, _this*/) {\n _clone = angular.copy(node);\n this.for_all_descendants(_clone, this.changeKey);\n return _clone;\n },\n loadChildren: function () {\n return null;\n },\n remove: function (node, parent, _this, delayReload) {\n var temp = parent.splice(node.__index__, 1)[0];\n if (!delayReload) {\n $scope.reload_data();\n }\n return temp;\n },\n clearInfo: function (node) {\n delete node.__inited__;\n delete node.__visible__;\n delete node.__icon__;\n delete node.__icon_class__;\n delete node.__level__;\n delete node.__index__;\n delete node.__index_real__;\n delete node.__parent_real__;\n delete node.__dept__;\n\n // always changed after call reload_data\n //delete node.__hashKey__;\n },\n add: function (node, pos, parent/*, _this*/) {\n // clearInfo\n this.for_all_descendants(node, this.clearInfo);\n if (parent) {\n if (parent.length > -1) {\n if (pos > -1) {\n parent.splice(pos, 0, node);\n } else {\n // todo If children need load crazy\n parent.push(node);\n }\n } else {\n parent.push(node);\n }\n }\n }\n };\n\n $scope.deleteScope = function (scope, node) {\n var _hash = node.__hashKey__;\n if ($scope.$globals[_hash] && $scope.$globals[_hash] === scope) {\n delete $scope.$globals[_hash];\n }\n };\n\n $scope.setScope = function (scope, node) {\n var _hash = node.__hashKey__;\n if ($scope.$globals[_hash] !== scope) {\n $scope.$globals[_hash] = scope;\n }\n };\n\n $scope.getScope = function (node) {\n if (node) {\n var _hash = node.__hashKey__;\n //var _hash = typeof node === 'string' ? node : node.__hashKey__;\n return $scope.$globals[_hash];\n }\n return $scope;\n };\n\n function nodeHasChildren(node) {\n return (angular.isArray(node.__children__) && node.__children__.length > 0) ||\n node.__has_children__ === true ||\n node.__lazy__ === true;\n }\n\n $scope.loadChildren = function (node) {\n if (!node || node.__loading__) {\n return $q.when([]);\n }\n\n node.__loading__ = true;\n\n return $q.when($scope.$callbacks.loadChildren(node)).then(\n function (children) {\n if (angular.isArray(children)) {\n node.__children__ = children;\n } else if (!angular.isArray(node.__children__)) {\n node.__children__ = [];\n }\n\n node.__lazy__ = false;\n node.__has_children__ = node.__children__.length > 0;\n node.__expanded__ = node.__children__.length > 0;\n reload_data();\n return node.__children__;\n }\n ).finally(function () {\n node.__loading__ = false;\n });\n };\n\n if ($attrs.enableDrag || $attrs.enableDrop) {\n $scope.placeElm = null;\n // $scope.dragBorder = 30;\n $scope.dragEnabled = null;\n $scope.dropEnabled = null;\n $scope.horizontal = null;\n\n if ($attrs.enableDrag) {\n\n $scope.dragDelay = 0;\n $scope.enabledMove = true;\n $scope.statusMove = true;\n $scope.enabledHotkey = false;\n $scope.enabledCollapse = null;\n $scope.statusElm = null;\n $scope.dragging = null;\n\n angular.extend(\n $scope.$callbacks, {\n beforeDrag: function (/*scopeDrag*/) {\n return true;\n },\n dragStop: function (info, passed) {\n if (!info || !info.changed && info.drag.enabledMove || !passed) {\n return null;\n }\n\n info.target.reload_data();\n\n if (info.target !== info.drag && info.drag.enabledMove) {\n info.drag.reload_data();\n }\n },\n dropped: function (info/*, pass*/) {\n if (!info) {\n return null;\n }\n\n var _node = info.node,\n _nodeAdd = null,\n _move = info.move,\n _parent = null,\n _parentRemove = info.parent || info.drag.treeData,\n _parentAdd = _move.parent || info.target.treeData,\n isMove = info.drag.enabledMove;\n\n if (!info.changed && isMove) {\n return false;\n }\n\n if (info.target.$callbacks.accept(info, info.move, info.changed)) {\n if (isMove) {\n _parent = _parentRemove;\n if (angular.isDefined(_parent.__children__)) {\n _parent = _parent.__children__;\n }\n\n _nodeAdd = info.drag.$callbacks.remove(\n _node,\n _parent,\n info.drag.$callbacks,\n true // delay reload\n );\n } else {\n _nodeAdd = info.drag.$callbacks.clone(_node, info.drag.$callbacks);\n }\n\n // if node dragging change index in sample node parent\n // and index node decrement\n if (isMove &&\n info.drag === info.target &&\n _parentRemove === _parentAdd &&\n _move.pos >= info.node.__index__) {\n _move.pos--;\n }\n\n _parent = _parentAdd;\n if (_parent.__children__) {\n _parent = _parent.__children__;\n }\n\n info.target.$callbacks.add(\n _nodeAdd,\n _move.pos,\n _parent,\n info.drag.$callbacks\n );\n\n return true;\n }\n\n return false;\n },\n dragStart: function (event) {\n },\n dragMove: function (event) {\n }\n }\n );\n\n $scope.setDragging = function (dragInfo) {\n $scope.dragging = dragInfo;\n };\n\n $scope.enableMove = function (val) {\n if (typeof val === 'boolean') {\n $scope.enabledMove = val;\n } else {\n $scope.enabledMove = true;\n }\n };\n\n if ($attrs.enableStatus) {\n $scope.enabledStatus = false;\n\n $scope.hideStatus = function () {\n if ($scope.statusElm) {\n $scope.statusElm.addClass($scope.$class.hidden);\n }\n };\n\n $scope.refreshStatus = function () {\n if (!$scope.dragging) {\n return;\n }\n\n if ($scope.enabledStatus) {\n var statusElmOld = $scope.statusElm;\n if ($scope.enabledMove) {\n $scope.statusElm = angular.element($TreeDnDTemplate.getMove($scope));\n } else {\n $scope.statusElm = angular.element($TreeDnDTemplate.getCopy($scope));\n }\n\n if (statusElmOld !== $scope.statusElm) {\n if (statusElmOld) {\n $scope.statusElm.attr('class', statusElmOld.attr('class'));\n $scope.statusElm.attr('style', statusElmOld.attr('style'));\n statusElmOld.remove();\n }\n $document.find('body').append($scope.statusElm);\n\n }\n\n $scope.statusElm.removeClass($scope.$class.hidden);\n }\n };\n\n $scope.setPositionStatus = function (e) {\n if ($scope.statusElm) {\n $scope.statusElm.css(\n {\n 'left': e.pageX + 10 + 'px',\n 'top': e.pageY + 15 + 'px',\n 'z-index': 9999\n }\n );\n $scope.statusElm.addClass($scope.$class.status);\n }\n };\n }\n }\n\n $scope.targeting = false;\n\n $scope.getPrevSibling = function (node) {\n if (node && node.__index__ > 0) {\n var _parent, _index = node.__index__ - 1;\n\n if (angular.isDefined(node.__parent_real__)) {\n _parent = $scope.tree_nodes[node.__parent_real__];\n return _parent.__children__[_index];\n }\n return $scope.treeData[_index];\n\n }\n return null;\n };\n\n $scope.getNode = function (index) {\n if (angular.isUndefinedOrNull(index)) {\n return null;\n }\n return $scope.tree_nodes[index];\n };\n\n $scope.initPlace = function (element, dragElm) {\n\n if (!$scope.placeElm) {\n if ($scope.isTable) {\n $scope.placeElm = angular.element($window.document.createElement('tr'));\n var _len_down = $scope.colDefinitions.length;\n $scope.placeElm.append(\n angular.element($window.document.createElement('td'))\n .addClass($scope.$class.empty)\n .addClass('indented')\n .addClass($scope.$class.place)\n );\n while (_len_down-- > 0) {\n $scope.placeElm.append(\n angular.element($window.document.createElement('td'))\n .addClass($scope.$class.empty)\n .addClass($scope.$class.place)\n );\n }\n } else {\n $scope.placeElm = angular.element($window.document.createElement('li'))\n .addClass($scope.$class.empty)\n .addClass($scope.$class.place);\n }\n\n }\n\n if (dragElm) {\n $scope.placeElm.css('height', $TreeDnDHelper.height(dragElm) + 'px');\n }\n\n if (element) {\n element[0].parentNode.insertBefore($scope.placeElm[0], element[0]);\n } else {\n $scope.getElementChilds().append($scope.placeElm);\n }\n\n return $scope.placeElm;\n };\n\n $scope.hidePlace = function () {\n if ($scope.placeElm) {\n $scope.placeElm.addClass($scope.$class.hidden);\n }\n };\n\n $scope.showPlace = function () {\n if ($scope.placeElm) {\n $scope.placeElm.removeClass($scope.$class.hidden);\n }\n };\n\n $scope.getScopeTree = function () {\n return $scope;\n };\n\n }\n\n $scope.$safeApply = $safeApply;\n\n $scope.hiddenChild = function fnHiddenChild(node, parent) {\n var nodeScope = $scope.getScope(node);\n if (nodeScope) {\n if (parent && parent.__expanded__ && parent.__visible__) {\n nodeScope.$element.removeClass($scope.$class.hidden);\n node.__visible__ = true;\n } else {\n nodeScope.$element.addClass($scope.$class.hidden);\n node.__visible__ = false;\n }\n } else {\n // show node & init scope\n if (parent && parent.__expanded__ && parent.__visible__) {\n node.__visible__ = true;\n } else {\n node.__visible__ = false;\n }\n }\n\n // skip all child hiding... if not expaned\n return node.__expanded__ === false;\n };\n\n var _fnInitFilter,\n _fnInitOrderBy,\n _fnGetControl,\n _defaultFilterOption = {\n showParent: true,\n showChild: false,\n beginAnd: true\n },\n tree,\n // Array to store watch deregistration functions for cleanup\n _watchDeregistrations = [],\n _watches = [\n [\n 'enableDrag',\n [\n ['boolean', 'enableStatus', null, 'enabledStatus'],\n ['boolean', 'enableMove', null, 'enabledMove'],\n ['number', 'dragDelay', 0, null, 0],\n ['boolean', 'enableCollapse', null, 'enabledCollapse'],\n ['boolean', 'enableHotkey', null, 'enabledHotkey', null, function (isHotkey) {\n if (isHotkey) {\n $scope.enabledMove = false;\n } else {\n $scope.enabledMove = $scope.statusMove;\n }\n }]\n ]\n ],\n [\n ['enableDrag', 'enableStatus'], [\n ['string', 'templateCopy', $attrs.templateCopy, 'templateCopy', null, function (_url) {\n if (_url && $templateCache.get(_url)) {\n $TreeDnDTemplate.setCopy(_url, $scope);\n }\n }],\n ['string', 'templateMove', $attrs.templateMove, 'templateMove', null, function (_url) {\n if (_url && $templateCache.get(_url)) {\n $TreeDnDTemplate.setMove(_url, $scope);\n }\n }]\n ]],\n [\n [['enableDrag', 'enableDrop']], [\n ['number', 'dragBorder', 30, 'dragBorder', 30]]\n ],\n [\n '*', [\n ['boolean', 'treeTable', true, 'treeTable', null],\n ['boolean', 'horizontal'],\n ['callback', 'treeClass', function (val) {\n switch (typeof val) {\n case 'string':\n $scope.$tree_class = val;\n break;\n case 'object':\n angular.extend($scope.$class, val);\n $scope.$tree_class = $scope.$class.tree;\n break;\n default:\n $scope.$tree_class = $attrs.treeClass;\n break;\n }\n }, 'treeClass', function () {\n $scope.$tree_class = $scope.$class.tree + ' table';\n }, null, function () {\n if (/^(\\s+[\\w\\-]+){2,}$/g.test(' ' + $attrs.treeClass)) {\n $scope.$tree_class = $attrs.treeClass.trim();\n return true;\n }\n }],\n [\n ['object', 'string'], 'expandOn', getExpandOn, 'expandingProperty', getExpandOn,\n function (expandOn) {\n if (angular.isUndefinedOrNull(expandOn)) {\n $scope.expandingProperty = $attrs.expandOn;\n }\n }],\n ['object', 'treeControl', angular.isDefined($scope.tree) ? $scope.tree : {},\n 'tree', null, function ($tree) {\n\n if (!angular.isFunction(_fnGetControl)) {\n _fnGetControl = $TreeDnDPlugin('$TreeDnDControl');\n }\n\n if (angular.isFunction(_fnGetControl)) {\n tree = angular.extend(\n $tree,\n _fnGetControl($scope)\n );\n }\n }],\n [\n ['array', 'object'], 'columnDefs', getColDefs, 'colDefinitions', getColDefs,\n function (colDefs) {\n if (angular.isUndefinedOrNull(colDefs) || !angular.isArray(colDefs)) {\n $scope.colDefinitions = getColDefs();\n }\n }],\n [\n ['object', 'string', 'array', 'function'], 'orderBy', $attrs.orderBy\n ],\n [\n ['object', 'array'], 'filter', null, 'filter', null, function (filters) {\n var _passed = false;\n if (angular.isDefined(filters) && !angular.isArray(filters)) {\n var _keysF = Object.keys(filters),\n _lenF = _keysF.length, _iF;\n\n if (_lenF > 0) {\n for (_iF = 0; _iF < _lenF; _iF++) {\n\n if (typeof filters[_keysF[_iF]] === 'string' &&\n filters[_keysF[_iF]].length === 0) {\n continue;\n }\n _passed = true;\n break;\n }\n }\n }\n\n $scope.enabledFilter = _passed;\n reload_data();\n }],\n [\n 'object', 'filterOptions', _defaultFilterOption, 'filterOptions',\n _defaultFilterOption, function (option) {\n if (angular.isObject(option)) {\n $scope.filterOptions = angular.extend(_defaultFilterOption, option);\n }\n }],\n ['string', 'primaryKey', $attrs.primaryKey, 'primary_key', '__uid__'],\n ['string', 'indentUnit', $attrs.indentUnit, 'indent_unit'],\n ['number', 'indent', 30, null, 30],\n ['number', 'indentPlus', 20, null, 20],\n ['null', 'callbacks', function (optCallbacks) {\n angular.forEach(\n optCallbacks, function (value, key) {\n if (typeof value === 'function') {\n if ($scope.$callbacks[key]) {\n $scope.$callbacks[key] = value;\n }\n }\n }\n );\n return $scope.$callbacks;\n },\n '$callbacks'\n ],\n ['number', 'expandLevel', 3, 'expandLevel', 3, function () {\n reload_data();\n }],\n ['number', 'treeLimit', 100, '$TreeLimit', 100],\n ['boolean', 'enableDrag', null, 'dragEnabled'],\n ['boolean', 'enableDrop', null, 'dropEnabled']\n ]]\n ],\n w, lenW = _watches.length,\n i, len,\n _curW,\n _typeW, _nameW, _defaultW, _scopeW, _NotW, _AfterW, _BeforeW,\n\n // debounce reload_Data;\n timeReloadData, tmpTreeData;\n\n for (w = 0; w < lenW; w++) {\n // skip if not exist\n if (!check_exist_attr($attrs, _watches[w][0], true)) {\n continue;\n }\n _curW = _watches[w][1];\n for (i = 0, len = _curW.length; i < len; i++) {\n _typeW = _curW[i][0];\n _nameW = _curW[i][1];\n _defaultW = _curW[i][2];\n _scopeW = _curW[i][3];\n _NotW = _curW[i][4];\n _AfterW = _curW[i][5];\n _BeforeW = _curW[i][6];\n generateWatch(_typeW, _nameW, _defaultW, _scopeW, _NotW, _AfterW, _BeforeW);\n }\n }\n\n if ($attrs.treeData) {\n // Store deregistration function for treeData watch\n var unwatchTreeData = $scope.$watch(\n $attrs.treeData, function (val) {\n if (angular.equals(val, $scope.treeData)) {\n return;\n }\n\n tmpTreeData = val;\n if (angular.isUndefinedOrNull(timeReloadData)) {\n timeReloadData = $timeout(timeLoadData, 350);\n }\n }, true\n );\n _watchDeregistrations.push(unwatchTreeData);\n }\n\n $scope.$on('$destroy', function () {\n // Cancel any pending timeouts\n if (timeReloadData) {\n $timeout.cancel(timeReloadData);\n timeReloadData = null;\n }\n tmpTreeData = null;\n\n // Deregister all watches to prevent memory leaks\n var i, len;\n for (i = 0, len = _watchDeregistrations.length; i < len; i++) {\n if (_watchDeregistrations[i]) {\n _watchDeregistrations[i]();\n }\n }\n _watchDeregistrations.length = 0;\n\n // Clear all scope references in $globals\n if ($scope.$globals) {\n var keys = Object.keys($scope.$globals);\n for (i = 0, len = keys.length; i < len; i++) {\n delete $scope.$globals[keys[i]];\n }\n $scope.$globals = null;\n }\n\n // Remove and clean up placeholder element\n if ($scope.placeElm) {\n $scope.placeElm.remove();\n $scope.placeElm = null;\n }\n\n // Remove and clean up status element\n if ($scope.statusElm) {\n $scope.statusElm.remove();\n $scope.statusElm = null;\n }\n\n // Clear tree node references\n if ($scope.tree_nodes) {\n $scope.tree_nodes.length = 0;\n $scope.tree_nodes = null;\n }\n\n // Clear treeData references\n if ($scope.treeData) {\n $scope.treeData = null;\n }\n\n // Clear callbacks to break circular references\n $scope.$callbacks = null;\n\n // Clear column definitions\n $scope.colDefinitions = null;\n\n // Clear tree control reference\n if (tree) {\n tree = null;\n }\n });\n\n function timeLoadData() {\n $scope.treeData = tmpTreeData;\n reload_data();\n timeReloadData = null;\n }\n\n $scope.updateLimit = function updateLimit() {\n //console.log('Call fn UpdateLimit');\n $scope.$TreeLimit += 50;\n };\n\n $scope.reload_data = reload_data;\n\n function check_exist_attr(attrs, existAttr, isAnd) {\n if (angular.isUndefinedOrNull(existAttr)) {\n return false;\n }\n\n if (existAttr === '*' || !angular.isUndefined(attrs[existAttr])) {\n return true;\n }\n\n if (angular.isArray(existAttr)) {\n return for_each_attrs(attrs, existAttr, isAnd);\n }\n }\n\n function for_each_attrs(attrs, exist, isAnd) {\n var i, len = exist.length, passed = false;\n\n if (len === 0) {\n return null;\n }\n for (i = 0; i < len; i++) {\n if (check_exist_attr(attrs, exist[i], !isAnd)) {\n passed = true;\n if (!isAnd) {\n return true;\n }\n } else {\n if (isAnd) {\n return false;\n }\n }\n }\n\n return passed;\n }\n\n function generateWatch(type, nameAttr, valDefault, nameScope, fnNotExist, fnAfter,\n fnBefore) {\n nameScope = nameScope || nameAttr;\n if (typeof type === 'string' || angular.isArray(type)) {\n if (angular.isFunction(fnBefore) && fnBefore()) {\n return;//jmp\n }\n if (typeof $attrs[nameAttr] === 'string') {\n // Store deregistration function for cleanup\n var unwatchFn = $scope.$watch(\n $attrs[nameAttr], function (val) {\n if (typeof type === 'string' && typeof val === type ||\n angular.isArray(type) && type.indexOf(typeof val) > -1\n ) {\n $scope[nameScope] = val;\n } else {\n if (angular.isFunction(valDefault)) {\n $scope[nameScope] = valDefault(val);\n } else {\n $scope[nameScope] = valDefault;\n }\n }\n\n if (angular.isFunction(fnAfter)) {\n fnAfter($scope[nameScope], $scope);\n }\n }, true\n );\n _watchDeregistrations.push(unwatchFn);\n } else {\n\n if (angular.isFunction(fnNotExist)) {\n $scope[nameScope] = fnNotExist();\n } else if (!angular.isUndefined(fnNotExist)) {\n $scope[nameScope] = fnNotExist;\n }\n }\n }\n }\n\n function $safeApply(fn) {\n var phase = this.$root.$$phase;\n if (phase === '$apply' || phase === '$digest') {\n if (fn && typeof fn === 'function') {\n fn();\n }\n } else {\n this.$apply(fn);\n }\n }\n\n function getExpandOn() {\n if ($scope.treeData && $scope.treeData.length) {\n var _firstNode = $scope.treeData[0], _keys = Object.keys(_firstNode),\n _regex = new RegExp('^__([a-zA-Z0-9_\\-]*)__$'),\n _len,\n i;\n // Auto get first field with type is string;\n for (i = 0, _len = _keys.length; i < _len; i++) {\n if (typeof _firstNode[_keys[i]] === 'string' && !_regex.test(_keys[i])) {\n $scope.expandingProperty = _keys[i];\n return;\n }\n }\n\n // Auto get first\n if (angular.isUndefinedOrNull($scope.expandingProperty)) {\n $scope.expandingProperty = _keys[0];\n }\n\n }\n }\n\n function getColDefs() {\n // Auto get Defs except attribute __level__ ....\n if ($scope.treeData.length) {\n var _col_defs = [], _firstNode = $scope.treeData[0],\n _regex = new RegExp('(^__([a-zA-Z0-9_\\-]*)__$|^' + $scope.expandingProperty + '$)'),\n _keys = Object.keys(_firstNode),\n i, _len;\n // Auto get first field with type is string;\n for (i = 0, _len = _keys.length; i < _len; i++) {\n if (typeof _firstNode[_keys[i]] === 'string' && !_regex.test(_keys[i])) {\n _col_defs.push(\n {\n field: _keys[i]\n }\n );\n }\n }\n $scope.colDefinitions = _col_defs;\n }\n }\n\n function do_f(root, node, parent, parent_real, level, visible, index) {\n\n if (typeof node !== 'object') {\n return 0;\n }\n\n var _i, _len, _icon, _index_real, _dept, _hashKey;\n if (!angular.isArray(node.__children__)) {\n node.__children__ = [];\n }\n\n node.__parent_real__ = parent_real;\n node.__parent__ = parent;\n _len = node.__children__.length;\n\n var _hasChildren = _len > 0 || node.__has_children__ === true || node.__lazy__ === true;\n\n if (angular.isUndefinedOrNull(node.__expanded__) && _hasChildren) {\n node.__expanded__ = level < $scope.expandLevel;\n }\n\n if (!_hasChildren) {\n _icon = -1;\n } else {\n if (node.__expanded__) {\n _icon = 1;\n } else {\n _icon = 0;\n }\n }\n\n // Insert item vertically\n _index_real = root.length;\n node.__index__ = index;\n node.__index_real__ = _index_real;\n node.__level__ = level;\n node.__icon__ = _icon;\n node.__icon_class__ = $scope.$class.icon[_icon];\n node.__visible__ = !!visible;\n\n if (angular.isUndefinedOrNull(node.__uid__)) {\n node.__uid__ = '' + Math.random();\n }\n\n _hashKey = $scope.getHash(node);\n\n if (angular.isUndefinedOrNull(node.__hashKey__) || node.__hashKey__ !== _hashKey) {\n node.__hashKey__ = _hashKey;\n }\n\n root.push(node);\n\n // Check node children\n _dept = 1;\n if (_len > 0) {\n for (_i = 0; _i < _len; _i++) {\n _dept += do_f(\n root,\n node.__children__[_i],\n node[$scope.primary_key],\n _index_real,\n level + 1,\n visible && node.__expanded__,\n _i\n );\n }\n }\n\n node.__dept__ = _dept;\n\n return _dept;\n }\n\n function init_data(data) {\n\n // clear memory - properly clean up old nodes to prevent memory leaks\n if (angular.isDefined($scope.tree_nodes) && $scope.tree_nodes) {\n // Clear internal properties that may hold references\n var i, len, node;\n for (i = 0, len = $scope.tree_nodes.length; i < len; i++) {\n node = $scope.tree_nodes[i];\n if (node) {\n // Clear the __inited__ flag that creates circular references\n delete node.__inited__;\n }\n }\n $scope.tree_nodes.length = 0;\n }\n\n $scope.tree_nodes = data;\n return data;\n }\n\n function reload_data(oData) {\n var _data,\n _len,\n _tree_nodes = [];\n if (angular.isDefined(oData)) {\n if (!angular.isArray(oData) || oData.length === 0) {\n return init_data([]);\n } else {\n _data = oData;\n }\n } else if (!angular.isArray($scope.treeData) || $scope.treeData.length === 0) {\n return init_data([]);\n } else {\n _data = $scope.treeData;\n }\n\n if (!$attrs.expandOn) {\n getExpandOn();\n }\n\n if (!$attrs.columnDefs) {\n getColDefs();\n }\n\n if (angular.isDefined($scope.orderBy)) {\n if (!angular.isFunction(_fnInitOrderBy)) {\n _fnInitOrderBy = $TreeDnDPlugin('$TreeDnDOrderBy');\n }\n\n if (angular.isFunction(_fnInitOrderBy)) {\n _data = _fnInitOrderBy(_data, $scope.orderBy);\n }\n }\n\n if (angular.isDefined($scope.filter)) {\n if (!angular.isFunction(_fnInitFilter)) {\n _fnInitFilter = $TreeDnDPlugin('$TreeDnDFilter');\n }\n\n if (angular.isFunction(_fnInitFilter)) {\n _data = _fnInitFilter(_data, $scope.filter, $scope.filterOptions);\n }\n }\n\n _len = _data.length;\n if (_len > 0) {\n var _i,\n _deptTotal = 0;\n\n for (_i = 0; _i < _len; _i++) {\n _deptTotal += do_f(_tree_nodes, _data[_i], null, null, 1, true, _i);\n }\n\n }\n\n init_data(_tree_nodes);\n\n return _tree_nodes;\n }\n }\n\n function fnCompile(tElement) {\n\n var $_Template = '',\n _element = tElement.html().trim();\n\n if (_element.length > 0) {\n $_Template = _element;\n tElement.html('');\n }\n\n return function fnPost(scope, element, attrs) {\n\n if (attrs.enableDrag) {\n var _fnInitDrag = $TreeDnDPlugin('$TreeDnDDrag');\n if (angular.isFunction(_fnInitDrag)) {\n _fnInitDrag(scope, element, $window, $document);\n }\n }\n\n // kick out $digest\n element.ready(function () {\n // apply Template\n function checkTreeTable(template, scope) {\n var elemNode = template[0].querySelector('[tree-dnd-node]'),\n attrInclude;\n\n scope.isTable = null;\n if (elemNode) {\n elemNode = angular.element(elemNode);\n attrInclude = elemNode.attr('ng-include');\n } else {\n return;\n }\n\n if (attrInclude) {\n var treeInclude = $parse(attrInclude)(scope) || attrInclude;\n if (typeof treeInclude === 'string') {\n return $http.get(\n treeInclude,\n {cache: $templateCache}\n ).then(function (response) {\n var data = response.data || '';\n data = data.trim();\n //scope.templateNode = data;\n var tempDiv = document.createElement('div');\n tempDiv.innerHTML = data;\n tempDiv = angular.element(tempDiv);\n scope.isTable = !tempDiv[0].querySelector('[tree-dnd-nodes]');\n }\n );\n }\n } else {\n scope.isTable = !elemNode[0].querySelector('[tree-dnd-nodes]');\n //scope.templateNode = elemNode.html();\n }\n $TreeDnDViewport.setTemplate(scope, scope.templateNode);\n //elemNode.html('');\n }\n\n //scope.$watch(tableDataLoaded, transformTable);\n /*\n function tableDataLoaded(elem) {\n // first cell in the tbody exists when data is loaded but doesn't have a width\n // until after the table is transformed\n var firstCell = elem.querySelector('tbody tr:first-child td:first-child');\n return firstCell && !firstCell.style.width;\n }\n\n function transformTable(elem, attrs) {\n // reset display styles so column widths are correct when measured below\n angular.element(elem.querySelectorAll('thead, tbody, tfoot')).css('display', '');\n\n // wrap in $timeout to give table a chance to finish rendering\n $timeout(function () {\n // set widths of columns\n angular.forEach(elem.querySelectorAll('tr:first-child th'), function (thElem, i) {\n\n var tdElems = elem.querySelector('tbody tr:first-child td:nth-child(' + (i + 1) + ')');\n var tfElems = elem.querySelector('tfoot tr:first-child td:nth-child(' + (i + 1) + ')');\n\n var columnWidth = tdElems ? tdElems.offsetWidth : thElem.offsetWidth;\n if (tdElems) {\n tdElems.style.width = columnWidth + 'px';\n }\n if (thElem) {\n thElem.style.width = columnWidth + 'px';\n }\n if (tfElems) {\n tfElems.style.width = columnWidth + 'px';\n }\n });\n\n // set css styles on thead and tbody\n angular.element(elem.querySelectorAll('thead, tfoot')).css('display', 'block');\n\n angular.element(elem.querySelectorAll('tbody')).css({\n 'display': 'block',\n 'height': attrs.tableHeight || 'inherit',\n 'overflow': 'auto'\n });\n\n // reduce width of last column by width of scrollbar\n var tbody = elem.querySelector('tbody');\n var scrollBarWidth = tbody.offsetWidth - tbody.clientWidth;\n if (scrollBarWidth > 0) {\n // for some reason trimming the width by 2px lines everything up better\n scrollBarWidth -= 2;\n var lastColumn = elem.querySelector('tbody tr:first-child td:last-child');\n lastColumn.style.width = lastColumn.offsetWidth - scrollBarWidth + 'px';\n }\n });\n }*/\n var promiseCheck;\n if ($_Template.length > 0) {\n promiseCheck = checkTreeTable(angular.element($_Template.trim()), scope);\n if (angular.isObject(promiseCheck)) {\n promiseCheck.then(function () {\n element.append($compile($_Template)(scope));\n });\n } else {\n element.append($compile($_Template)(scope));\n }\n } else {\n $http.get(\n attrs.templateUrl || $TreeDnDTemplate.getPath(),\n {cache: $templateCache}\n ).then(function (response) {\n var data = response.data || '';\n data = angular.element(data.trim());\n promiseCheck = checkTreeTable(data, scope);\n if (angular.isObject(promiseCheck)) {\n promiseCheck.then(function () {\n element.append($compile(data)(scope));\n });\n } else {\n element.append($compile(data)(scope));\n }\n }\n );\n }\n })\n };\n }\n}\n\n\nangular.module('ntt.TreeDnD')\n .factory('$TreeDnDDrag', [\n '$timeout', '$TreeDnDHelper',\n function ($timeout, $TreeDnDHelper) {\n function _fnPlaceHolder(e, $params) {\n if ($params.placeElm) {\n var _offset = $TreeDnDHelper.offset($params.placeElm);\n if (_offset.top <= e.pageY && e.pageY <= _offset.top + _offset.height &&\n _offset.left <= e.pageX && e.pageX <= _offset.left + _offset.width\n ) {\n return true;\n }\n }\n return false;\n }\n\n function _fnDragStart(e, $params) {\n if (!$params.hasTouch && (e.button === 2 || e.which === 3)) {\n // disable right click\n return;\n }\n\n if (e.uiTreeDragging || e.originalEvent && e.originalEvent.uiTreeDragging) { // event has already fired in other scope.\n return;\n }\n\n // the element which is clicked.\n var eventElm = angular.element(e.target),\n eventScope = eventElm.scope();\n if (!eventScope || !eventScope.$type) {\n return;\n }\n // if (eventScope.$type !== 'TreeDnDNode') { // Check if it is a node or a handle\n // return;\n // }\n\n if (eventScope.$type !== 'TreeDnDNodeHandle') { // If the node has a handle, then it should be clicked by the handle\n return;\n }\n\n var eventElmTagName = eventElm.prop('tagName').toLowerCase(),\n dragScope,\n _$scope = $params.$scope;\n if (eventElmTagName === 'input'\n || eventElmTagName === 'textarea'\n || eventElmTagName === 'button'\n || eventElmTagName === 'select') { // if it's a input or button, ignore it\n return;\n }\n // check if it or it's parents has a 'data-nodrag' attribute\n while (eventElm && eventElm[0] && eventElm[0] !== $params.element) {\n if ($TreeDnDHelper.nodrag(eventElm)) { // if the node mark as `nodrag`, DONOT drag it.\n return;\n }\n eventElm = eventElm.parent();\n }\n\n e.uiTreeDragging = true; // stop event bubbling\n if (e.originalEvent) {\n e.originalEvent.uiTreeDragging = true;\n }\n e.preventDefault();\n\n dragScope = eventScope.getScopeNode();\n\n $params.dragInfo = $TreeDnDHelper.dragInfo(dragScope);\n\n if (!_$scope.$callbacks.beforeDrag(dragScope, $params.dragInfo)) {\n return;\n }\n\n $params.firstMoving = true;\n _$scope.setDragging($params.dragInfo);\n\n var eventObj = $TreeDnDHelper.eventObj(e);\n $params.pos = $TreeDnDHelper.positionStarted(eventObj, dragScope.$element);\n\n if (dragScope.isTable) {\n $params.dragElm = angular.element($params.$window.document.createElement('table'))\n .addClass(_$scope.$class.tree)\n .addClass(_$scope.$class.drag)\n .addClass(_$scope.$tree_class);\n } else {\n $params.dragElm = angular.element($params.$window.document.createElement('ul'))\n .addClass(_$scope.$class.drag)\n .addClass('tree-dnd-nodes')\n .addClass(_$scope.$tree_class);\n }\n\n $params.dragElm.css(\n {\n 'width': $TreeDnDHelper.width(dragScope.$element) + 'px',\n 'z-index': 9995\n }\n );\n\n $params.offsetEdge = 0;\n var _width = $TreeDnDHelper.width(dragScope.$element),\n _scope = dragScope,\n _element = _scope.$element,\n _clone,\n _needCollapse = !!_$scope.enabledCollapse,\n _copied = false,\n _tbody,\n _frag;\n\n if (_scope.isTable) {\n $params.offsetEdge = $params.dragInfo.node.__level__ - 1;\n _tbody = angular.element(document.createElement('tbody'));\n _frag = angular.element(document.createDocumentFragment());\n\n _$scope.for_all_descendants(\n $params.dragInfo.node, function (_node, _parent) {\n _scope = _$scope.getScope(_node);\n _element = _scope && _scope.$element;\n if (_scope && _element) {\n if (!_copied) {\n _clone = _element.clone();\n\n $TreeDnDHelper.replaceIndent(\n _$scope,\n _clone,\n _node.__level__ - $params.offsetEdge,\n 'padding-left'\n );\n\n _frag.append(_clone);\n\n // skip all, just clone parent\n if (_needCollapse) {\n _copied = true;\n }\n\n // hide if have status Move;\n if (_$scope.enabledMove && _$scope.$class.hidden &&\n (!_parent || _node.__visible__ || _parent.__visible__ && _parent.__expanded__)) {\n _element.addClass(_$scope.$class.hidden);\n }\n }\n }\n // skip children of node not expand.\n return _copied || _node.__visible__ === false || _node.__expanded__ === false;\n\n }, null, !_needCollapse\n );\n _tbody.append(_frag);\n $params.dragElm.append(_tbody);\n } else {\n\n _clone = _element.clone();\n if (_needCollapse) {\n _clone[0].querySelector('[tree-dnd-nodes]').remove();\n }\n\n // hide if have status Move;\n $params.dragElm.append(_clone);\n if (_$scope.enabledMove && _$scope.$class.hidden) {\n _element.addClass(_$scope.$class.hidden);\n }\n }\n\n $params.dragElm.css(\n {\n 'left': eventObj.pageX - $params.pos.offsetX + _$scope.$callbacks.calsIndent(\n $params.offsetEdge + 1,\n true,\n true\n ) + 'px',\n 'top': eventObj.pageY - $params.pos.offsetY + 'px'\n }\n );\n // moving item with descendant\n $params.$document.find('body').append($params.dragElm);\n if (_$scope.$callbacks.droppable()) {\n $params.placeElm = _$scope.initPlace(dragScope.$element, $params.dragElm);\n\n if (dragScope.isTable) {\n $TreeDnDHelper.replaceIndent(_$scope, $params.placeElm, $params.dragInfo.node.__level__);\n }\n\n $params.placeElm.css('width', _width);\n }\n\n _$scope.showPlace();\n _$scope.targeting = true;\n\n if (_$scope.enabledStatus) {\n _$scope.refreshStatus();\n _$scope.setPositionStatus(e);\n }\n\n angular.element($params.$document).bind('touchend', $params.dragEndEvent);\n angular.element($params.$document).bind('touchcancel', $params.dragEndEvent);\n angular.element($params.$document).bind('touchmove', $params.dragMoveEvent);\n angular.element($params.$document).bind('mouseup', $params.dragEndEvent);\n angular.element($params.$document).bind('mousemove', $params.dragMoveEvent);\n angular.element($params.$document).bind('mouseleave', $params.dragCancelEvent);\n\n $params.document_height = Math.max(\n $params.body.scrollHeight,\n $params.body.offsetHeight,\n $params.html.clientHeight,\n $params.html.scrollHeight,\n $params.html.offsetHeight\n );\n\n $params.document_width = Math.max(\n $params.body.scrollWidth,\n $params.body.offsetWidth,\n $params.html.clientWidth,\n $params.html.scrollWidth,\n $params.html.offsetWidth\n );\n }\n\n function _fnDragMove(e, $params) {\n var _$scope = $params.$scope;\n if (!$params.dragStarted) {\n if (!$params.dragDelaying) {\n $params.dragStarted = true;\n _$scope.$safeApply(\n function () {\n _$scope.$callbacks.dragStart($params.dragInfo);\n }\n );\n }\n return;\n }\n\n if ($params.dragElm) {\n e.preventDefault();\n if ($params.$window.getSelection) {\n $params.$window.getSelection().removeAllRanges();\n } else if ($params.$window.document.selection) {\n $params.$window.document.selection.empty();\n }\n\n var eventObj = $TreeDnDHelper.eventObj(e),\n leftElmPos = eventObj.pageX - $params.pos.offsetX,\n topElmPos = eventObj.pageY - $params.pos.offsetY;\n\n //dragElm can't leave the screen on the left\n if (leftElmPos < 0) {\n leftElmPos = 0;\n }\n\n //dragElm can't leave the screen on the top\n if (topElmPos < 0) {\n topElmPos = 0;\n }\n\n //dragElm can't leave the screen on the bottom\n if (topElmPos + 10 > $params.document_height) {\n topElmPos = $params.document_height - 10;\n }\n\n //dragElm can't leave the screen on the right\n if (leftElmPos + 10 > $params.document_width) {\n leftElmPos = $params.document_width - 10;\n }\n\n $params.dragElm.css(\n {\n 'left': leftElmPos + _$scope.$callbacks.calsIndent(\n $params.offsetEdge + 1,\n true,\n true\n ) + 'px',\n 'top': topElmPos + 'px'\n }\n );\n\n if (_$scope.enabledStatus) {\n _$scope.setPositionStatus(e);\n }\n\n var top_scroll = window.pageYOffset || $params.$window.document.documentElement.scrollTop,\n bottom_scroll = top_scroll + (window.innerHeight || $params.$window.document.clientHeight || $params.$window.document.clientHeight);\n // to scroll down if cursor y-position is greater than the bottom position the vertical scroll\n if (bottom_scroll < eventObj.pageY && bottom_scroll <= $params.document_height) {\n window.scrollBy(0, 10);\n }\n // to scroll top if cursor y-position is less than the top position the vertical scroll\n if (top_scroll > eventObj.pageY) {\n window.scrollBy(0, -10);\n }\n\n $TreeDnDHelper.positionMoved(e, $params.pos, $params.firstMoving);\n\n if ($params.firstMoving) {\n $params.firstMoving = false;\n return;\n }\n // check if add it as a child node first\n\n var targetX = eventObj.pageX - $params.$window.document.body.scrollLeft,\n targetY = eventObj.pageY - (window.pageYOffset || $params.$window.document.documentElement.scrollTop),\n\n targetElm,\n targetScope,\n targetBefore,\n targetOffset,\n isChanged = true,\n isVeritcal = true,\n isEmpty,\n isSwapped,\n _scope,\n _target,\n _parent,\n _info = $params.dragInfo,\n _move = _info.move,\n _drag = _info.node,\n _drop = _info.drop,\n treeScope = _info.target,\n fnSwapTree,\n isHolder = _fnPlaceHolder(e, $params);\n\n if (!isHolder) {\n /* when using elementFromPoint() inside an iframe, you have to call\n elementFromPoint() twice to make sure IE8 returns the correct value\n $params.$window.document.elementFromPoint(targetX, targetY);*/\n\n targetElm = angular.element(\n $params.$window.document.elementFromPoint(\n targetX,\n targetY\n )\n );\n\n targetScope = targetElm.scope();\n if (!targetScope || !targetScope.$callbacks || !targetScope.$callbacks.droppable()) {\n // Not allowed Drop Item\n return;\n }\n\n fnSwapTree = function () {\n treeScope = targetScope.getScopeTree();\n _target = _info.target;\n\n if (_info.target !== treeScope) {\n // Replace by place-holder new\n _target.hidePlace();\n _target.targeting = false;\n treeScope.targeting = true;\n\n _info.target = treeScope;\n $params.placeElm = treeScope.initPlace(targetScope.$element, $params.dragElm);\n\n _target = null;\n isSwapped = true;\n }\n return true;\n };\n\n if (angular.isFunction(targetScope.getScopeNode)) {\n targetScope = targetScope.getScopeNode();\n if (!fnSwapTree()) {\n return;\n }\n } else {\n if (targetScope.$type === 'TreeDnDNodes' || targetScope.$type === 'TreeDnD') {\n if (targetScope.tree_nodes) {\n if (targetScope.tree_nodes.length === 0) {\n if (!fnSwapTree()) {\n return;\n }\n // Empty\n isEmpty = true;\n }\n } else {\n return;\n }\n } else {\n return;\n }\n }\n }\n\n if ($params.pos.dirAx && !isSwapped || isHolder) {\n isVeritcal = false;\n targetScope = _info.scope;\n }\n\n if (!targetScope.$element && !targetScope) {\n return;\n }\n\n if (isEmpty) {\n _move.parent = null;\n _move.pos = 0;\n\n _drop = null;\n } else {\n // move vertical\n if (isVeritcal) {\n targetElm = targetScope.$element; // Get the element of tree-dnd-node\n if (angular.isUndefinedOrNull(targetElm)) {\n return;\n }\n targetOffset = $TreeDnDHelper.offset(targetElm);\n\n if (targetScope.horizontal && !targetScope.isTable) {\n targetBefore = eventObj.pageX < targetOffset.left + $TreeDnDHelper.width(targetElm) / 2;\n } else {\n if (targetScope.isTable) {\n targetBefore = eventObj.pageY < targetOffset.top + $TreeDnDHelper.height(targetElm) / 2;\n } else {\n var _height = $TreeDnDHelper.height(targetElm);\n\n if (targetScope.getElementChilds()) {\n _height -= -$TreeDnDHelper.height(targetScope.getElementChilds());\n }\n\n if (eventObj.pageY > targetOffset.top + _height) {\n return;\n }\n\n targetBefore = eventObj.pageY < targetOffset.top + _height / 2;\n }\n }\n\n if (!angular.isFunction(targetScope.getData)) {\n return;\n }\n\n _target = targetScope.getData();\n _parent = targetScope.getNode(_target.__parent_real__);\n\n if (targetBefore) {\n var _prev = targetScope.getPrevSibling(_target);\n\n _move.parent = _parent;\n _move.pos = angular.isDefined(_prev) ? _prev.__index__ + 1 : 0;\n\n _drop = _prev;\n } else {\n if (_target.__expanded__ && !(_target.__children__.length === 1 && _target.__index_real__ === _drag.__parent_real__)) {\n _move.parent = _target;\n _move.pos = 0;\n\n _drop = null;\n } else {\n _move.parent = _parent;\n _move.pos = _target.__index__ + 1;\n\n _drop = _target;\n }\n }\n } else {\n // move horizontal\n if ($params.pos.dirAx && $params.pos.distAxX >= treeScope.dragBorder) {\n $params.pos.distAxX = 0;\n // increase horizontal level if previous sibling exists and is not collapsed\n if ($params.pos.distX > 0) {\n _parent = _drop;\n if (!_parent) {\n if (_move.pos - 1 >= 0) {\n _parent = _move.parent.__children__[_move.pos - 1];\n } else {\n return;\n }\n }\n\n if (_info.drag === _info.target && _parent === _drag && _$scope.enabledMove) {\n _parent = treeScope.getPrevSibling(_parent);\n }\n\n if (_parent && _parent.__visible__) {\n var _len = _parent.__children__.length;\n\n _move.parent = _parent;\n _move.pos = _len;\n\n if (_len > 0) {\n _drop = _parent.__children__[_len - 1];\n } else {\n _drop = null;\n }\n } else {\n // Not changed\n return;\n }\n } else if ($params.pos.distX < 0) {\n _target = _move.parent;\n if (_target &&\n (_target.__children__.length === 0 ||\n _target.__children__.length - 1 < _move.pos ||\n _info.drag === _info.target &&\n _target.__index_real__ === _drag.__parent_real__ &&\n _target.__children__.length - 1 === _drag.__index__ && _$scope.enabledMove)\n ) {\n _parent = treeScope.getNode(_target.__parent_real__);\n\n _move.parent = _parent;\n _move.pos = _target.__index__ + 1;\n\n _drop = _target;\n } else {\n // Not changed\n return;\n }\n } else {\n return;\n }\n } else {\n // limited\n return;\n }\n }\n }\n\n if (_info.drag === _info.target &&\n _move.parent &&\n _drag.__parent_real__ === _move.parent.__index_real__ &&\n _drag.__index__ === _move.pos\n ) {\n isChanged = false;\n }\n\n if (treeScope.$callbacks.accept(_info, _move, isChanged)) {\n _info.move = _move;\n _info.drop = _drop;\n _info.changed = isChanged;\n _info.scope = targetScope;\n\n if (targetScope.isTable) {\n $TreeDnDHelper.replaceIndent(\n treeScope,\n $params.placeElm,\n angular.isUndefinedOrNull(_move.parent) ? 1 : _move.parent.__level__ + 1\n );\n\n if (_drop) {\n _parent = (_move.parent ? _move.parent.__children__ : null) || _info.target.treeData;\n\n if (_drop.__index__ < _parent.length - 1) {\n // Find fast\n _drop = _parent[_drop.__index__ + 1];\n _scope = _info.target.getScope(_drop);\n _scope.$element[0].parentNode.insertBefore(\n $params.placeElm[0],\n _scope.$element[0]\n );\n } else {\n _target = _info.target.getLastDescendant(_drop);\n _scope = _info.target.getScope(_target);\n _scope.$element.after($params.placeElm);\n }\n } else {\n _scope = _info.target.getScope(_move.parent);\n if (_scope) {\n if (_move.parent) {\n _scope.$element.after($params.placeElm);\n\n } else {\n _scope.getElementChilds().prepend($params.placeElm);\n }\n }\n }\n } else {\n _scope = _info.target.getScope(_drop || _move.parent);\n if (_drop) {\n _scope.$element.after($params.placeElm);\n } else {\n _scope.getElementChilds().prepend($params.placeElm);\n }\n }\n\n treeScope.showPlace();\n\n _$scope.$safeApply(\n function () {\n _$scope.$callbacks.dragMove(_info);\n }\n );\n }\n\n }\n }\n\n function _fnDragEnd(e, $params) {\n e.preventDefault();\n\n // Always unbind document listeners first to prevent leaks\n // even if dragElm is null (edge case handling)\n _fnUnbindDocumentListeners($params);\n\n if ($params.dragElm) {\n var _passed = false,\n _$scope = $params.$scope,\n _scope = _$scope.getScope($params.dragInfo.node),\n _element = _scope.$element;\n\n _$scope.$safeApply(\n function () {\n _passed = _$scope.$callbacks.beforeDrop($params.dragInfo);\n }\n );\n\n // rollback all\n if (_scope.isTable) {\n _$scope.for_all_descendants(\n $params.dragInfo.node, function (_node, _parent) {\n _scope = _$scope.getScope(_node);\n _element = _scope && _scope.$element;\n if (_scope && _element && (!_parent && _node.__visible__ || _parent.__expanded__)) {\n if (_$scope.$class.hidden) {\n _element.removeClass(_$scope.$class.hidden);\n }\n }\n return _node.__visible__ === false || _node.__expanded__ === false\n }, null, true\n );\n } else {\n if (_$scope.$class.hidden) {\n _element.removeClass(_$scope.$class.hidden);\n }\n }\n\n $params.dragElm.remove();\n $params.dragElm = null;\n\n if (_$scope.enabledStatus) {\n _$scope.hideStatus();\n }\n\n if (_$scope.$$apply) {\n _$scope.$safeApply(\n function () {\n var _status = _$scope.$callbacks.dropped(\n $params.dragInfo,\n _passed\n );\n\n _$scope.$callbacks.dragStop($params.dragInfo, _status);\n clearData();\n }\n );\n } else {\n _fnBindDrag($params);\n _$scope.$safeApply(\n function () {\n _$scope.$callbacks.dragStop($params.dragInfo, false);\n clearData();\n }\n );\n }\n\n }\n\n function clearData() {\n if ($params.dragInfo && $params.dragInfo.target) {\n $params.dragInfo.target.hidePlace();\n $params.dragInfo.target.targeting = false;\n }\n\n $params.dragInfo = null;\n if ($params.$scope) {\n $params.$scope.$$apply = false;\n $params.$scope.setDragging(null);\n }\n }\n }\n\n /**\n * Unbind all document-level event listeners\n * Separated into its own function to ensure proper cleanup\n */\n function _fnUnbindDocumentListeners($params) {\n angular.element($params.$document).unbind('touchend', $params.dragEndEvent);\n angular.element($params.$document).unbind('touchcancel', $params.dragEndEvent);\n angular.element($params.$document).unbind('touchmove', $params.dragMoveEvent);\n angular.element($params.$document).unbind('mouseup', $params.dragEndEvent);\n angular.element($params.$document).unbind('mousemove', $params.dragMoveEvent);\n angular.element($params.$window.document.body).unbind('mouseleave', $params.dragCancelEvent);\n }\n\n function _fnDragStartEvent(e, $params) {\n if ($params.$scope.$callbacks.draggable()) {\n _fnDragStart(e, $params);\n }\n }\n\n function _fnBindDrag($params) {\n $params.element.bind(\n 'touchstart mousedown', function (e) {\n $params.dragDelaying = true;\n $params.dragStarted = false;\n _fnDragStartEvent(e, $params);\n $params.dragTimer = $timeout(\n function () {\n $params.dragDelaying = false;\n }, $params.$scope.dragDelay\n );\n }\n );\n\n $params.element.bind(\n 'touchend touchcancel mouseup', function () {\n $timeout.cancel($params.dragTimer);\n }\n );\n }\n\n function _fnKeydownHandler(e, $params) {\n var _$scope = $params.$scope;\n if (e.keyCode === 27) {\n if (_$scope.enabledStatus) {\n _$scope.hideStatus();\n }\n\n _$scope.$$apply = false;\n _fnDragEnd(e, $params);\n } else {\n if (_$scope.enabledHotkey && e.shiftKey) {\n _$scope.enableMove(true);\n if (_$scope.enabledStatus) {\n _$scope.refreshStatus();\n }\n\n if (!$params.dragInfo) {\n return;\n }\n\n var _scope = _$scope.getScope($params.dragInfo.node),\n _element = _scope.$element;\n\n if (_scope.isTable) {\n _$scope.for_all_descendants(\n $params.dragInfo.node, function (_node, _parent) {\n _scope = _$scope.getScope(_node);\n _element = _scope && _scope.$element;\n if (_scope && _element && (!_parent && _node.__visible__ || _parent.__expanded__)) {\n if (_$scope.$class.hidden) {\n _element.addClass(_$scope.$class.hidden);\n }\n }\n return _node.__visible__ === false || _node.__expanded__ === false\n\n }, null, true\n );\n } else {\n if (_$scope.$class.hidden) {\n _element.addClass(_$scope.$class.hidden);\n }\n }\n }\n }\n }\n\n function _fnKeyupHandler(e, $params) {\n var _$scope = $params.$scope;\n if (_$scope.enabledHotkey && !e.shiftKey) {\n _$scope.enableMove(false);\n\n if (_$scope.enabledStatus) {\n _$scope.refreshStatus();\n }\n\n if (!$params.dragInfo) {\n return;\n }\n\n var _scope = _$scope.getScope($params.dragInfo.node),\n _element = _scope.$element;\n\n if (_scope.isTable) {\n _$scope.for_all_descendants(\n $params.dragInfo.node, function (_node, _parent) {\n _scope = _$scope.getScope(_node);\n _element = _scope && _scope.$element;\n if (_scope && _element && (!_parent && _node.__visible__ || _parent.__expanded__)) {\n if (_$scope.$class.hidden) {\n _element.removeClass(_$scope.$class.hidden);\n }\n }\n return _node.__visible__ === false || _node.__expanded__ === false\n }, null, true\n );\n } else {\n if (_$scope.$class.hidden) {\n _element.removeClass(_$scope.$class.hidden);\n }\n }\n }\n }\n\n function _$init(scope, element, $window, $document) {\n var $params = {\n hasTouch: 'ontouchstart' in window,\n firstMoving: null,\n dragInfo: null,\n pos: null,\n placeElm: null,\n dragElm: null,\n dragDelaying: true,\n dragStarted: false,\n dragTimer: null,\n body: document.body,\n html: document.documentElement,\n document_height: null,\n document_width: null,\n offsetEdge: null,\n $scope: scope,\n $window: $window,\n $document: $document,\n element: element,\n bindDrag: function () {\n _fnBindDrag($params);\n },\n dragEnd: function (e) {\n _fnDragEnd(e, $params);\n },\n dragMoveEvent: function (e) {\n _fnDragMove(e, $params);\n },\n dragEndEvent: function (e) {\n scope.$$apply = true;\n _fnDragEnd(e, $params);\n },\n dragCancelEvent: function (e) {\n _fnDragEnd(e, $params);\n }\n },\n keydownHandler = function (e) {\n return _fnKeydownHandler(e, $params);\n },\n keyupHandler = function (e) {\n return _fnKeyupHandler(e, $params);\n };\n\n scope.dragEnd = function (e) {\n $params.dragEnd(e);\n };\n\n $params.bindDrag();\n\n angular.element($window.document.body).bind('keydown', keydownHandler);\n angular.element($window.document.body).bind('keyup', keyupHandler);\n //unbind handler that retains scope\n scope.$on(\n '$destroy', function () {\n // Unbind keyboard handlers\n angular.element($window.document.body).unbind('keydown', keydownHandler);\n angular.element($window.document.body).unbind('keyup', keyupHandler);\n\n // Unbind element drag listeners\n $params.element.unbind('touchstart mousedown');\n $params.element.unbind('touchend touchcancel mouseup');\n\n // Unbind any active document listeners (in case drag is in progress)\n _fnUnbindDocumentListeners($params);\n\n // Cancel any pending drag timer\n if ($params.dragTimer) {\n $timeout.cancel($params.dragTimer);\n $params.dragTimer = null;\n }\n\n // Remove and clean up drag element if still present\n if ($params.dragElm) {\n $params.dragElm.remove();\n $params.dragElm = null;\n }\n\n // Clean up status element\n if (scope.statusElm) {\n scope.statusElm.remove();\n scope.statusElm = null;\n }\n\n // Clean up placeholder element\n if (scope.placeElm) {\n scope.placeElm.remove();\n scope.placeElm = null;\n }\n\n // Clear dragInfo to break circular references\n $params.dragInfo = null;\n $params.pos = null;\n $params.placeElm = null;\n\n // Clear $params scope reference\n $params.$scope = null;\n $params.element = null;\n }\n );\n }\n\n return _$init;\n }\n ]);\n\nangular.module('ntt.TreeDnD')\n .factory('$TreeDnDControl', function () {\n var _target, _parent,\n i, len;\n\n function fnSetCollapse(node) {\n node.__expanded__ = false;\n }\n\n function fnSetExpand(node) {\n node.__expanded__ = true;\n }\n\n function _$init(scope) {\n var n, tree = {\n selected_node: null,\n for_all_descendants: scope.for_all_descendants,\n select_node: function (node) {\n if (!node) {\n if (tree.selected_node) {\n delete tree.selected_node.__selected__;\n }\n tree.selected_node = null;\n return null;\n }\n\n if (node !== tree.selected_node) {\n if (tree.selected_node) {\n delete tree.selected_node.__selected__;\n }\n node.__selected__ = true;\n tree.selected_node = node;\n tree.expand_all_parents(node);\n if (angular.isFunction(tree.on_select)) {\n tree.on_select(node);\n }\n }\n\n return node;\n },\n deselect_node: function () {\n _target = null;\n if (tree.selected_node) {\n delete tree.selected_node.__selected__;\n _target = tree.selected_node;\n tree.selected_node = null;\n }\n return _target;\n },\n get_parent: function (node) {\n node = node || tree.selected_node;\n\n if (node && node.__parent_real__ !== null) {\n return scope.tree_nodes[node.__parent_real__];\n }\n return null;\n },\n for_all_ancestors: function (node, fn) {\n _parent = tree.get_parent(node);\n if (_parent) {\n if (fn(_parent)) {\n return false;\n }\n\n return tree.for_all_ancestors(_parent, fn);\n }\n return true;\n },\n expand_all_parents: function (node) {\n node = node || tree.selected_node;\n\n if (angular.isObject(node)) {\n tree.for_all_ancestors(node, fnSetExpand);\n }\n },\n collapse_all_parents: function (node) {\n node = node || tree.selected_node;\n if (angular.isObject(node)) {\n tree.for_all_ancestors(node, fnSetCollapse);\n }\n },\n\n reload_data: function () {\n return scope.reload_data();\n },\n add_node: function (parent, new_node, index) {\n if (typeof index !== 'number') {\n if (parent) {\n parent.__children__.push(new_node);\n parent.__expanded__ = true;\n } else {\n scope.treeData.push(new_node);\n }\n } else {\n if (parent) {\n parent.__children__.splice(index, 0, new_node);\n parent.__expanded__ = true;\n } else {\n scope.treeData.splice(index, 0, new_node);\n }\n }\n return new_node;\n },\n add_node_root: function (new_node) {\n tree.add_node(null, new_node);\n return new_node;\n },\n expand_all: function () {\n len = scope.treeData.length;\n for (i = 0; i < len; i++) {\n tree.for_all_descendants(scope.treeData[i], fnSetExpand);\n }\n },\n collapse_all: function () {\n len = scope.treeData.length;\n for (i = 0; i < len; i++) {\n tree.for_all_descendants(scope.treeData[i], fnSetCollapse);\n }\n },\n remove_node: function (node) {\n node = node || tree.selected_node;\n\n if (angular.isObject(node)) {\n if (node.__parent_real__ !== null) {\n _parent = tree.get_parent(node).__children__;\n } else {\n _parent = scope.treeData;\n }\n\n _parent.splice(node.__index__, 1);\n\n tree.reload_data();\n\n if (tree.selected_node === node) {\n tree.selected_node = null;\n }\n }\n },\n expand_node: function (node) {\n node = node || tree.selected_node;\n\n if (angular.isObject(node)) {\n node.__expanded__ = true;\n return node;\n }\n },\n collapse_node: function (node) {\n node = node || tree.selected_node;\n\n if (angular.isObject(node)) {\n node.__expanded__ = false;\n return node;\n }\n },\n get_selected_node: function () {\n return tree.selected_node;\n },\n get_first_node: function () {\n len = scope.treeData.length;\n if (len > 0) {\n return scope.treeData[0];\n }\n\n return null;\n },\n get_children: function (node) {\n node = node || tree.selected_node;\n\n return node.__children__;\n },\n get_siblings: function (node) {\n node = node || tree.selected_node;\n if (angular.isObject(node)) {\n _parent = tree.get_parent(node);\n if (_parent) {\n _target = _parent.__children__;\n } else {\n _target = scope.treeData;\n }\n return _target;\n }\n },\n get_next_sibling: function (node) {\n node = node || tree.selected_node;\n if (angular.isObject(node)) {\n _target = tree.get_siblings(node);\n n = _target.length;\n if (node.__index__ < n) {\n return _target[node.__index__ + 1];\n }\n }\n },\n get_prev_sibling: function (node) {\n node = node || tree.selected_node;\n _target = tree.get_siblings(node);\n if (node.__index__ > 0) {\n return _target[node.__index__ - 1];\n }\n },\n get_first_child: function (node) {\n node = node || tree.selected_node;\n if (angular.isObject(node)) {\n _target = node.__children__;\n if (_target && _target.length > 0) {\n return node.__children__[0];\n }\n }\n return null;\n },\n get_closest_ancestor_next_sibling: function (node) {\n node = node || tree.selected_node;\n _target = tree.get_next_sibling(node);\n if (_target) {\n return _target;\n }\n\n _parent = tree.get_parent(node);\n if (_parent) {\n return tree.get_closest_ancestor_next_sibling(_parent);\n }\n\n return null;\n },\n get_next_node: function (node) {\n node = node || tree.selected_node;\n\n if (angular.isObject(node)) {\n _target = tree.get_first_child(node);\n if (_target) {\n return _target;\n } else {\n return tree.get_closest_ancestor_next_sibling(node);\n }\n }\n },\n get_prev_node: function (node) {\n node = node || tree.selected_node;\n\n if (angular.isObject(node)) {\n _target = tree.get_prev_sibling(node);\n if (_target) {\n return tree.get_last_descendant(_target);\n }\n\n _parent = tree.get_parent(node);\n return _parent;\n }\n },\n get_last_descendant: scope.getLastDescendant,\n select_parent_node: function (node) {\n node = node || tree.selected_node;\n\n if (angular.isObject(node)) {\n _parent = tree.get_parent(node);\n if (_parent) {\n return tree.select_node(_parent);\n }\n }\n },\n select_first_node: function () {\n var firstNode = tree.get_first_node();\n return tree.select_node(firstNode);\n },\n select_next_sibling: function (node) {\n node = node || tree.selected_node;\n\n if (angular.isObject(node)) {\n _target = tree.get_next_sibling(node);\n if (_target) {\n return tree.select_node(_target);\n }\n }\n },\n select_prev_sibling: function (node) {\n node = node || tree.selected_node;\n\n if (angular.isObject(node)) {\n _target = tree.get_prev_sibling(node);\n if (_target) {\n return tree.select_node(_target);\n }\n }\n },\n select_next_node: function (node) {\n node = node || tree.selected_node;\n\n if (angular.isObject(node)) {\n _target = tree.get_next_node(node);\n if (_target) {\n return tree.select_node(_target);\n }\n }\n },\n select_prev_node: function (node) {\n node = node || tree.selected_node;\n\n if (angular.isObject(node)) {\n _target = tree.get_prev_node(node);\n if (_target) {\n return tree.select_node(_target);\n }\n }\n }\n };\n angular.extend(scope.tree, tree);\n return scope.tree;\n }\n\n return _$init;\n });\n\nangular.module('ntt.TreeDnD')\n .factory('$TreeDnDFilter', [\n '$filter', function ($filter) {\n return fnInitFilter;\n\n function for_all_descendants(options, node, fieldChild, fnBefore, fnAfter, parentPassed) {\n if (!angular.isFunction(fnBefore)) {\n return null;\n }\n\n var _i, _len, _nodes,\n _nodePassed = fnBefore(options, node),\n _childPassed = false,\n _filter_index = options.filter_index;\n\n if (angular.isDefined(node[fieldChild])) {\n _nodes = node[fieldChild];\n _len = _nodes.length;\n\n options.filter_index = 0;\n for (_i = 0; _i < _len; _i++) {\n _childPassed = for_all_descendants(\n options,\n _nodes[_i],\n fieldChild,\n fnBefore,\n fnAfter,\n _nodePassed || parentPassed\n ) || _childPassed;\n }\n\n // restore filter_index of node\n options.filter_index = _filter_index;\n }\n\n if (angular.isFunction(fnAfter)) {\n fnAfter(options, node, _nodePassed === true, _childPassed === true, parentPassed === true);\n }\n\n return _nodePassed || _childPassed;\n }\n\n /**\n * Check data with callback\n * @param {string|object|function|regex} callback\n * @param {*} data\n * @returns {null|boolean}\n * @private\n */\n function _fnCheck(callback, data) {\n if (angular.isUndefinedOrNull(data) || angular.isArray(data)) {\n return null;\n }\n\n if (angular.isFunction(callback)) {\n return callback(data, $filter);\n } else {\n if (typeof callback === 'boolean') {\n data = !!data;\n return data === callback;\n } else if (angular.isDefined(callback)) {\n try {\n var _regex = new RegExp(callback);\n return _regex.test(data);\n }\n catch (err) {\n if (typeof data === 'string') {\n return data.indexOf(callback) > -1;\n } else {\n return null;\n }\n }\n } else {\n return null;\n }\n }\n }\n\n /**\n * `fnProcess` to call `_fnCheck`. If `condition` is `array` then call `for_each_filter`\n * else will call `_fnCheck`. Specical `condition.field` is `_$` then apply `condition.callback` for all field, if have `field` invaild then `return true`.\n *\n * @param node\n * @param condition\n * @param isAnd\n * @returns {null|boolean}\n * @private\n */\n function _fnProccess(node, condition, isAnd) {\n if (angular.isArray(condition)) {\n return for_each_filter(node, condition, isAnd);\n } else {\n var _key = condition.field,\n _callback = condition.callback,\n _iO, _keysO, _lenO;\n\n if (_key === '_$') {\n _keysO = Object.keys(node);\n _lenO = _keysO.length;\n for (_iO = 0; _iO < _lenO; _iO++) {\n if (_fnCheck(_callback, node[_keysO[_iO]])) {\n return true;\n }\n }\n } else if (angular.isDefined(node[_key])) {\n return _fnCheck(_callback, node[_key]);\n }\n }\n return null;\n }\n\n /**\n *\n * @param {object} node\n * @param {array} conditions Array `conditions`\n * @param {boolean} isAnd check with condition `And`, if `And` then `return false` when all `false`\n * @returns {null|boolean}\n */\n function for_each_filter(node, conditions, isAnd) {\n var i, len = conditions.length || 0, passed = false;\n if (len === 0) {\n return null;\n }\n\n for (i = 0; i < len; i++) {\n if (_fnProccess(node, conditions[i], !isAnd)) {\n passed = true;\n // if condition `or` then return;\n if (!isAnd) {\n return true;\n }\n } else {\n\n // if condition `and` and result in fnProccess = false then return;\n if (isAnd) {\n return false;\n }\n }\n }\n\n return passed;\n }\n\n /**\n * Will call _fnAfter to clear data no need\n * @param {object} options\n * @param {object} node\n * @param {boolean} isNodePassed\n * @param {boolean} isChildPassed\n * @param {boolean} isParentPassed\n * @private\n */\n function _fnAfter(options, node, isNodePassed, isChildPassed, isParentPassed) {\n if (isNodePassed === true) {\n node.__filtered__ = true;\n node.__filtered_visible__ = true;\n node.__filtered_index__ = options.filter_index++;\n return; //jmp\n } else if (isChildPassed === true && options.showParent === true\n || isParentPassed === true && options.showChild === true) {\n node.__filtered__ = false;\n node.__filtered_visible__ = true;\n node.__filtered_index__ = options.filter_index++;\n return; //jmp\n }\n\n // remove attr __filtered__\n delete node.__filtered__;\n delete node.__filtered_visible__;\n delete node.__filtered_index__;\n }\n\n /**\n * `fnBefore` will called when `for_all_descendants` of `node` checking.\n * If `filter` empty then return `true` else result of function `_fnProccess` {@see _fnProccess}\n *\n * @param {object} options\n * @param {object} node\n * @returns {null|boolean}\n * @private\n */\n function _fnBefore(options, node) {\n if (options.filter.length === 0) {\n return true;\n } else {\n return _fnProccess(node, options.filter, options.beginAnd || false);\n }\n }\n\n /**\n * `fnBeforeClear` will called when `for_all_descendants` of `node` checking.\n * Alway false to Clear Filter empty\n *\n * @param {object} options\n * @param {object} node\n * @returns {null|boolean}\n * @private\n */\n function _fnBeforeClear(options, node) {\n return false;\n }\n\n /**\n * `_fnConvert` to convert `filter` `object` to `array` invaild.\n *\n * @param {object|array} filters\n * @returns {array} Instead of `filter` or new array invaild *(converted from filter)*\n * @private\n */\n function _fnConvert(filters) {\n var _iF, _lenF, _keysF,\n _filter,\n _state;\n // convert filter object to array filter\n if (angular.isObject(filters) && !angular.isArray(filters)) {\n _keysF = Object.keys(filters);\n _lenF = _keysF.length;\n _filter = [];\n\n if (_lenF > 0) {\n for (_iF = 0; _iF < _lenF; _iF++) {\n\n if (typeof filters[_keysF[_iF]] === 'string' && filters[_keysF[_iF]].length === 0) {\n continue;\n } else if (angular.isArray(filters[_keysF[_iF]])) {\n _state = filters[_keysF[_iF]];\n } else if (angular.isObject(filters[_keysF[_iF]])) {\n _state = _fnConvert(filters[_keysF[_iF]]);\n } else {\n _state = {\n field: _keysF[_iF],\n callback: filters[_keysF[_iF]]\n };\n }\n _filter.push(_state);\n }\n }\n _state = null;\n return _filter;\n }\n else {\n return filters;\n }\n }\n\n /**\n * `fnInitFilter` function is constructor of service `$TreeDnDFilter`.\n * @constructor\n * @param {object|array} treeData\n * @param {object|array} filters\n * @param {object} options\n * @param {string} keyChild\n * @returns {array} Return `treeData` or `treeData` with `filter`\n * @private\n */\n function fnInitFilter(treeData, filters, options, keyChild) {\n if (!angular.isArray(treeData)\n || treeData.length === 0) {\n return treeData;\n }\n\n var _i, _len,\n _filter;\n\n _filter = _fnConvert(filters);\n if (!(angular.isArray(_filter) || angular.isObject(_filter))\n || _filter.length === 0) {\n for (_i = 0, _len = treeData.length; _i < _len; _i++) {\n for_all_descendants(\n options,\n treeData[_i],\n keyChild || '__children__',\n _fnBeforeClear, _fnAfter\n );\n }\n return treeData;\n }\n\n options.filter = _filter;\n options.filter_index = 0;\n for (_i = 0, _len = treeData.length; _i < _len; _i++) {\n for_all_descendants(\n options,\n treeData[_i],\n keyChild || '__children__',\n _fnBefore, _fnAfter\n );\n }\n\n return treeData;\n }\n\n }]\n );\n\nangular.module('ntt.TreeDnD')\n .factory('$TreeDnDOrderBy', [\n '$filter',\n function ($filter) {\n var _fnOrderBy = $filter('orderBy'),\n for_all_descendants = function for_all_descendants(options, node, name, fnOrderBy) {\n var _i, _len, _nodes;\n\n if (angular.isDefined(node[name])) {\n _nodes = node[name];\n _len = _nodes.length;\n // OrderBy children\n for (_i = 0; _i < _len; _i++) {\n _nodes[_i] = for_all_descendants(options, _nodes[_i], name, fnOrderBy);\n }\n\n node[name] = fnOrderBy(node[name], options);\n }\n return node;\n },\n _fnOrder = function _fnOrder(list, orderBy) {\n return _fnOrderBy(list, orderBy);\n },\n _fnMain = function _fnMain(treeData, orderBy) {\n if (!angular.isArray(treeData)\n || treeData.length === 0\n || !(angular.isArray(orderBy) || angular.isObject(orderBy) || angular.isString(orderBy) || angular.isFunction(orderBy))\n || orderBy.length === 0 && !angular.isFunction(orderBy)) {\n return treeData;\n }\n\n var _i, _len;\n\n for (_i = 0, _len = treeData.length; _i < _len; _i++) {\n treeData[_i] = for_all_descendants(\n orderBy,\n treeData[_i],\n '__children__',\n _fnOrder\n );\n }\n\n return _fnOrder(treeData, orderBy);\n };\n\n return _fnMain;\n }]\n );\n\nangular.module('ntt.TreeDnD')\n .factory('$TreeDnDConvert', function () {\n var _$initConvert = {\n line2tree: function (data, primaryKey, parentKey, callback) {\n callback = typeof callback === 'function' ? callback : function () {\n };\n if (!data || data.length === 0 || !primaryKey || !parentKey) {\n return [];\n }\n var tree = [],\n rootIds = [],\n item = data[0],\n _primary = item[primaryKey],\n treeObjs = {},\n parentId, parent,\n len = data.length,\n i = 0;\n\n while (i < len) {\n item = data[i++];\n callback(item);\n _primary = item[primaryKey];\n treeObjs[_primary] = item;\n }\n i = 0;\n while (i < len) {\n item = data[i++];\n callback(item);\n _primary = item[primaryKey];\n treeObjs[_primary] = item;\n parentId = item[parentKey];\n if (parentId) {\n parent = treeObjs[parentId];\n if (parent) {\n if (parent.__children__) {\n parent.__children__.push(item);\n } else {\n parent.__children__ = [item];\n }\n }\n } else {\n rootIds.push(_primary);\n }\n }\n len = rootIds.length;\n for (i = 0; i < len; i++) {\n tree.push(treeObjs[rootIds[i]]);\n }\n return tree;\n },\n tree2tree: function access_child(data, containKey, callback) {\n callback = typeof callback === 'function' ? callback : function () {\n };\n var _tree = [],\n _i,\n _len = data ? data.length : 0,\n _copy, _child;\n for (_i = 0; _i < _len; _i++) {\n _copy = angular.copy(data[_i]);\n callback(_copy);\n if (angular.isArray(_copy[containKey]) && _copy[containKey].length > 0) {\n _child = access_child(_copy[containKey], containKey, callback);\n delete _copy[containKey];\n _copy.__children__ = _child;\n }\n _tree.push(_copy);\n }\n return _tree;\n }\n };\n\n return _$initConvert;\n });\n\nangular.module('ntt.TreeDnD')\n .factory('$TreeDnDHelper', [\n '$document', '$window',\n function ($document, $window) {\n var _$helper = {\n nodrag: function (targetElm) {\n return typeof targetElm.attr('data-nodrag') !== 'undefined';\n },\n eventObj: function (e) {\n var obj = e;\n if (e.targetTouches !== undefined) {\n obj = e.targetTouches.item(0);\n } else if (e.originalEvent !== undefined && e.originalEvent.targetTouches !== undefined) {\n obj = e.originalEvent.targetTouches.item(0);\n }\n return obj;\n },\n dragInfo: function (scope) {\n var _node = scope.getData(),\n _tree = scope.getScopeTree(),\n _parent = scope.getNode(_node.__parent_real__);\n\n return {\n node: _node,\n parent: _parent,\n move: {\n parent: _parent,\n pos: _node.__index__\n },\n scope: scope,\n target: _tree,\n drag: _tree,\n drop: scope.getPrevSibling(_node),\n changed: false\n };\n },\n height: function (element) {\n return element.prop('scrollHeight');\n },\n width: function (element) {\n return element.prop('scrollWidth');\n },\n offset: function (element) {\n var boundingClientRect = element[0].getBoundingClientRect();\n return {\n width: element.prop('offsetWidth'),\n height: element.prop('offsetHeight'),\n top: boundingClientRect.top + ($window.pageYOffset || $document[0].body.scrollTop || $document[0].documentElement.scrollTop),\n left: boundingClientRect.left + ($window.pageXOffset || $document[0].body.scrollLeft || $document[0].documentElement.scrollLeft)\n };\n },\n positionStarted: function (e, target) {\n return {\n offsetX: e.pageX - this.offset(target).left,\n offsetY: e.pageY - this.offset(target).top,\n startX: e.pageX,\n lastX: e.pageX,\n startY: e.pageY,\n lastY: e.pageY,\n nowX: 0,\n nowY: 0,\n distX: 0,\n distY: 0,\n dirAx: 0,\n dirX: 0,\n dirY: 0,\n lastDirX: 0,\n lastDirY: 0,\n distAxX: 0,\n distAxY: 0\n };\n },\n positionMoved: function (e, pos, firstMoving) {\n // mouse position last events\n pos.lastX = pos.nowX;\n pos.lastY = pos.nowY;\n\n // mouse position this events\n pos.nowX = e.pageX;\n pos.nowY = e.pageY;\n\n // distance mouse moved between events\n pos.distX = pos.nowX - pos.lastX;\n pos.distY = pos.nowY - pos.lastY;\n\n // direction mouse was moving\n pos.lastDirX = pos.dirX;\n pos.lastDirY = pos.dirY;\n\n // direction mouse is now moving (on both axis)\n pos.dirX = pos.distX === 0 ? 0 : pos.distX > 0 ? 1 : -1;\n pos.dirY = pos.distY === 0 ? 0 : pos.distY > 0 ? 1 : -1;\n\n // axis mouse is now moving on\n var newAx = Math.abs(pos.distX) > Math.abs(pos.distY) ? 1 : 0;\n\n // do nothing on first move\n if (firstMoving) {\n pos.dirAx = newAx;\n pos.moving = true;\n return;\n }\n\n // calc distance moved on this axis (and direction)\n if (pos.dirAx !== newAx) {\n pos.distAxX = 0;\n pos.distAxY = 0;\n } else {\n pos.distAxX += Math.abs(pos.distX);\n if (pos.dirX !== 0 && pos.dirX !== pos.lastDirX) {\n pos.distAxX = 0;\n }\n pos.distAxY += Math.abs(pos.distY);\n if (pos.dirY !== 0 && pos.dirY !== pos.lastDirY) {\n pos.distAxY = 0;\n }\n }\n pos.dirAx = newAx;\n },\n replaceIndent: function (scope, element, indent, attr) {\n attr = attr || 'left';\n angular.element(element.children()[0]).css(attr, scope.$callbacks.calsIndent(indent));\n }\n };\n\n return _$helper;\n }]\n );\n\nangular.module('ntt.TreeDnD')\n .factory('$TreeDnDPlugin', [\n '$injector',\n function ($injector) {\n var _fnget = function (name) {\n if (angular.isDefined($injector) && $injector.has(name)) {\n return $injector.get(name);\n }\n return null;\n };\n return _fnget;\n }]\n );\n\nangular.module('ntt.TreeDnD')\n .factory('$TreeDnDTemplate', [\n '$templateCache',\n function ($templateCache) {\n var templatePath = 'template/TreeDnD/TreeDnD.html',\n copyPath = 'template/TreeDnD/TreeDnDStatusCopy.html',\n movePath = 'template/TreeDnD/TreeDnDStatusMove.html',\n scopes = {},\n temp,\n _$init = {\n setMove: function (path, scope) {\n if (!scopes[scope.$id]) {\n scopes[scope.$id] = {};\n }\n scopes[scope.$id].movePath = path;\n },\n setCopy: function (path, scope) {\n if (!scopes[scope.$id]) {\n scopes[scope.$id] = {};\n }\n scopes[scope.$id].copyPath = path;\n },\n getPath: function () {\n return templatePath;\n },\n getCopy: function (scope) {\n if (scopes[scope.$id] && scopes[scope.$id].copyPath) {\n temp = $templateCache.get(scopes[scope.$id].copyPath);\n if (temp) {\n return temp;\n }\n }\n return $templateCache.get(copyPath);\n },\n getMove: function (scope) {\n if (scopes[scope.$id] && scopes[scope.$id].movePath) {\n temp = $templateCache.get(scopes[scope.$id].movePath);\n if (temp) {\n return temp;\n }\n }\n return $templateCache.get(movePath);\n }\n };\n\n return _$init;\n }]\n );\n\nangular.module('ntt.TreeDnD')\n .factory('$TreeDnDViewport', fnInitTreeDnDViewport);\n\nfnInitTreeDnDViewport.$inject = ['$window', '$document', '$timeout', '$q', '$compile'];\n\nfunction fnInitTreeDnDViewport($window, $document, $timeout, $q, $compile) {\n\n var viewport = null,\n isUpdating = false,\n isRender = false,\n updateAgain = false,\n viewportRect,\n items = [],\n nodeTemplate,\n updateTimeout,\n renderTime,\n windowListenersBound = false,\n $initViewport = {\n setViewport: setViewport,\n getViewport: getViewport,\n add: add,\n remove: remove,\n setTemplate: setTemplate,\n getItems: getItems,\n updateDelayed: updateDelayed,\n destroy: destroy\n },\n eWindow = angular.element($window);\n\n return $initViewport;\n\n /**\n * Bind window event listeners (lazily on first add)\n */\n function bindWindowListeners() {\n if (!windowListenersBound) {\n eWindow.on('load resize scroll', updateDelayed);\n windowListenersBound = true;\n }\n }\n\n /**\n * Unbind window event listeners and clean up resources\n */\n function destroy() {\n if (windowListenersBound) {\n eWindow.off('load resize scroll', updateDelayed);\n windowListenersBound = false;\n }\n\n // Cancel any pending timeouts\n if (updateTimeout) {\n $timeout.cancel(updateTimeout);\n updateTimeout = null;\n }\n if (renderTime) {\n $timeout.cancel(renderTime);\n renderTime = null;\n }\n\n // Clear all item references\n items.length = 0;\n viewport = null;\n nodeTemplate = null;\n isUpdating = false;\n isRender = false;\n updateAgain = false;\n }\n\n function update() {\n\n viewportRect = {\n width: eWindow.prop('offsetWidth') || document.documentElement.clientWidth,\n height: eWindow.prop('offsetHeight') || document.documentElement.clientHeight,\n top: $document[0].body.scrollTop || $document[0].documentElement.scrollTop,\n left: $document[0].body.scrollLeft || $document[0].documentElement.scrollLeft\n };\n\n if (isUpdating || isRender) {\n updateAgain = true;\n return;\n }\n isUpdating = true;\n\n recursivePromise();\n }\n\n function recursivePromise() {\n if (isRender) {\n return;\n }\n\n var number = number > 0 ? number : items.length, item;\n\n if (number > 0) {\n item = items[0];\n\n isRender = true;\n renderTime = $timeout(function () {\n //item.element.html(nodeTemplate);\n //$compile(item.element.contents())(item.scope);\n\n items.splice(0, 1);\n isRender = false;\n number--;\n $timeout.cancel(renderTime);\n recursivePromise();\n }, 0);\n\n } else {\n isUpdating = false;\n if (updateAgain) {\n updateAgain = false;\n update();\n }\n }\n\n }\n\n /**\n * Check if a point is inside specified bounds\n * @param x\n * @param y\n * @param bounds\n * @returns {boolean}\n */\n function pointIsInsideBounds(x, y, bounds) {\n return x >= bounds.left &&\n y >= bounds.top &&\n x <= bounds.left + bounds.width &&\n y <= bounds.top + bounds.height;\n }\n\n /**\n * @name setViewport\n * @desciption Set the viewport element\n * @param element\n */\n function setViewport(element) {\n viewport = element;\n }\n\n /**\n * Return the current viewport\n * @returns {*}\n */\n function getViewport() {\n return viewport;\n }\n\n /**\n * trigger an update\n */\n function updateDelayed() {\n $timeout.cancel(updateTimeout);\n updateTimeout = $timeout(function () {\n update();\n }, 0);\n }\n\n /**\n * Add listener for event\n * @param element\n * @param callback\n */\n function add(scope, element) {\n // Lazily bind window listeners on first add\n bindWindowListeners();\n updateDelayed();\n items.push({\n element: element,\n scope: scope\n });\n }\n\n function remove(scope, element) {\n var i = items.length;\n while (i--) {\n if (items[i].scope === scope || (element && items[i].element === element)) {\n // Clear references before removing\n items[i].scope = null;\n items[i].element = null;\n items.splice(i, 1);\n }\n }\n // Auto-cleanup: unbind window listeners when no items remain\n if (items.length === 0 && windowListenersBound) {\n eWindow.off('load resize scroll', updateDelayed);\n windowListenersBound = false;\n }\n }\n\n function setTemplate(scope, template) {\n nodeTemplate = template;\n }\n\n /**\n * Get list of items\n * @returns {Array}\n */\n function getItems() {\n return items;\n }\n}\n\n\nangular.module('template/TreeDnD/TreeDnD.html', []).run(\n ['$templateCache', function ($templateCache) {\n $templateCache.put(\n 'template/TreeDnD/TreeDnD.html',\n ['',\n ' ',\n ' ',\n ' ',\n ' ',\n ' ',\n ' ',\n ' ',\n ' ',\n ' ',\n ' ',\n ' ',\n '
',\n ' {{expandingProperty.displayName || expandingProperty.field || expandingProperty}}',\n ' <\\/th>',\n ' ',\n ' {{col.displayName || col.field}}',\n '
',\n ' ',\n ' ',\n ' ',\n ' {{node[expandingProperty.field] || node[expandingProperty]}}',\n ' ',\n ' {{node[col.field]}}',\n '
'].join('\\n')\n );\n\n $templateCache.put(\n 'template/TreeDnD/TreeDnDStatusCopy.html',\n ''\n );\n\n $templateCache.put(\n 'template/TreeDnD/TreeDnDStatusMove.html',\n ''\n );\n }]\n);\n\n\n function isUndefinedOrNull(val) {\n return angular.isUndefined(val) || val === null;\n }\n\n function isDefined(val) {\n return !(angular.isUndefined(val) || val === null);\n }\n})();"],"names":["fnInitTreeDnD","$timeout","$http","$compile","$parse","$window","$document","$templateCache","$q","$TreeDnDTemplate","$TreeDnDClass","$TreeDnDHelper","$TreeDnDPlugin","$TreeDnDViewport","restrict","scope","replace","controller","$scope","$element","$attrs","_fnGetHash","node","__parent__","primary_key","passedExpand","_clone","indent","indent_plus","indent_unit","$tree_class","$type","colDefinitions","$globals","$class","treeData","tree_nodes","angular","copy","extend","icon","1","iconExpand","0","iconCollapse","-1","iconLeaf","for_all_descendants","fn","parent","checkSibling","isFunction","_i","_len","_nodes","__children__","length","getLastDescendant","n","tree","selected_node","last_child","getElementChilds","element","querySelector","onClick","isDefined","on_click","setTimeout","onSelect","select_node","on_select","toggleExpand","fnCallback","$callbacks","expand","__expanded__","isArray","__has_children__","__lazy__","loadChildren","getHash","setHash","_hashKey","isUndefinedOrNull","__hashKey__","accept","dropEnabled","calsIndent","level","skipUnit","skipEdge","unit","edge","droppable","draggable","dragEnabled","beforeDrop","changeKey","_key","__uid__","Math","random","__selected__","clone","this","remove","_this","delayReload","temp","splice","__index__","reload_data","clearInfo","__inited__","__visible__","__icon__","__icon_class__","__level__","__index_real__","__parent_real__","__dept__","add","pos","push","deleteScope","_hash","setScope","getScope","__loading__","when","then","children","finally","enableDrag","enableDrop","placeElm","horizontal","dragDelay","enabledMove","statusMove","enabledHotkey","enabledCollapse","statusElm","dragging","beforeDrag","dragStop","info","passed","changed","drag","target","dropped","_node","_nodeAdd","_move","_parent","_parentRemove","_parentAdd","isMove","move","dragStart","event","dragMove","setDragging","dragInfo","enableMove","val","enableStatus","enabledStatus","hideStatus","addClass","hidden","refreshStatus","statusElmOld","getMove","getCopy","attr","find","append","removeClass","setPositionStatus","e","css","left","pageX","top","pageY","z-index","status","targeting","getPrevSibling","_index","getNode","index","initPlace","dragElm","isTable","document","createElement","_len_down","empty","place","height","parentNode","insertBefore","hidePlace","showPlace","getScopeTree","$safeApply","phase","$root","$$phase","$apply","hiddenChild","nodeScope","_fnInitFilter","_fnInitOrderBy","_fnGetControl","w","i","len","_curW","_typeW","_nameW","_defaultW","_scopeW","_NotW","_AfterW","_BeforeW","timeReloadData","tmpTreeData","_defaultFilterOption","showParent","showChild","beginAnd","_watchDeregistrations","_watches","isHotkey","templateCopy","_url","get","setCopy","templateMove","setMove","treeClass","test","trim","getExpandOn","expandOn","expandingProperty","$tree","getColDefs","colDefs","orderBy","filters","_passed","_iF","_keysF","Object","keys","_lenF","enabledFilter","option","isObject","filterOptions","primaryKey","indentUnit","optCallbacks","forEach","value","key","lenW","check_exist_attr","type","nameAttr","valDefault","nameScope","fnNotExist","fnAfter","fnBefore","unwatchFn","$watch","indexOf","isUndefined","unwatchTreeData","equals","timeLoadData","attrs","existAttr","isAnd","for_each_attrs","exist","_firstNode","_keys","_regex","RegExp","_col_defs","field","init_data","data","oData","_data","_tree_nodes","columnDefs","filter","do_f","root","parent_real","visible","_index_real","_dept","_hasChildren","expandLevel","_icon","$on","cancel","updateLimit","$TreeLimit","compile","tElement","$_Template","_element","html","_fnInitDrag","ready","checkTreeTable","template","elemNode","attrInclude","treeInclude","cache","response","tempDiv","innerHTML","setTemplate","templateNode","promiseCheck","templateUrl","getPath","fnInitTreeDnDViewport","updateTimeout","renderTime","viewport","isUpdating","isRender","updateAgain","items","windowListenersBound","$initViewport","setViewport","getViewport","eWindow","on","updateDelayed","off","nodeTemplate","getItems","destroy","update","prop","documentElement","clientWidth","clientHeight","body","scrollTop","scrollLeft","recursivePromise","number","module","constant","nodes","handle","directive","link","unwatchCompile","new_val","unwatchCompileReplace","compileReplace","replaceWith","$node_class","childsElem","enabledDnD","keyNode","treeDndNode","first","getData","getScopeNode","objprops","keyO","lenO","hashKey","skipAttr","keepAttr","lenKeep","objexpr","join","unwatchNode","newVal","oldVal","nodeOf","parentReal","_childs","_hasChilds","$nodes_class","$inject","factory","_fnDragMove","$params","_$scope","dragStarted","preventDefault","getSelection","removeAllRanges","selection","eventObj","leftElmPos","offsetX","topElmPos","offsetY","top_scroll","document_height","document_width","offsetEdge","window","pageYOffset","bottom_scroll","innerHeight","scrollBy","positionMoved","firstMoving","targetElm","targetScope","targetBefore","isSwapped","_scope","targetX","targetY","isChanged","isVeritcal","_info","_drag","_drop","drop","treeScope","isHolder","_offset","offset","width","elementFromPoint","fnSwapTree","_target","isEmpty","dirAx","targetOffset","_height","_prev","distAxX","dragBorder","distX","replaceIndent","after","prepend","dragDelaying","_fnDragEnd","clearData","$$apply","_fnUnbindDocumentListeners","_status","_fnBindDrag","unbind","dragEndEvent","dragMoveEvent","dragCancelEvent","_fnDragStartEvent","_fnDragStart","hasTouch","button","which","uiTreeDragging","originalEvent","eventElm","eventScope","_width","_needCollapse","_copied","_tbody","_frag","eventElmTagName","toLowerCase","nodrag","dragScope","positionStarted","createDocumentFragment","bind","max","scrollHeight","offsetHeight","scrollWidth","offsetWidth","dragTimer","keydownHandler","keyCode","shiftKey","keyupHandler","bindDrag","dragEnd","fnSetCollapse","fnSetExpand","expand_all_parents","deselect_node","get_parent","for_all_ancestors","collapse_all_parents","add_node","new_node","add_node_root","expand_all","collapse_all","remove_node","expand_node","collapse_node","get_selected_node","get_first_node","get_children","get_siblings","get_next_sibling","get_prev_sibling","get_first_child","get_closest_ancestor_next_sibling","get_next_node","get_prev_node","get_last_descendant","select_parent_node","select_first_node","firstNode","select_next_sibling","select_prev_sibling","select_next_node","select_prev_node","$filter","options","keyChild","_filter","_fnConvert","_state","callback","_fnBeforeClear","_fnAfter","filter_index","_fnBefore","fieldChild","parentPassed","_nodePassed","_childPassed","_filter_index","_fnCheck","err","_fnProccess","condition","for_each_filter","conditions","_iO","_keysO","_lenO","_callback","isNodePassed","isChildPassed","isParentPassed","__filtered__","__filtered_visible__","__filtered_index__","name","fnOrderBy","_fnOrder","list","_fnOrderBy","isString","line2tree","parentKey","rootIds","item","_primary","treeObjs","parentId","tree2tree","access_child","containKey","_copy","_child","_tree","obj","undefined","targetTouches","boundingClientRect","getBoundingClientRect","pageXOffset","startX","lastX","startY","lastY","nowX","nowY","distY","dirX","dirY","lastDirX","lastDirY","distAxY","newAx","abs","moving","$injector","has","scopes","path","$id","movePath","copyPath","run","put"],"mappings":"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"} \ No newline at end of file diff --git a/src/directive/angular-tree-dnd-compile.js b/src/directive/angular-tree-dnd-compile.js index de1cd89..891ee4c 100644 --- a/src/directive/angular-tree-dnd-compile.js +++ b/src/directive/angular-tree-dnd-compile.js @@ -5,7 +5,8 @@ angular.module('ntt.TreeDnD') return { restrict: 'A', link: function (scope, element, attrs) { - scope.$watch( + // Store the deregistration function to prevent memory leaks + var unwatchCompile = scope.$watch( attrs.compile, function (new_val) { if (new_val) { if (angular.isFunction(element.empty)) { @@ -18,6 +19,14 @@ angular.module('ntt.TreeDnD') } } ); + + // Clean up watch on scope destroy + scope.$on('$destroy', function () { + if (unwatchCompile) { + unwatchCompile(); + unwatchCompile = null; + } + }); } }; }] @@ -28,14 +37,23 @@ angular.module('ntt.TreeDnD') return { restrict: 'A', link: function (scope, element, attrs) { - scope.$watch( + // Store the deregistration function to prevent memory leaks + var unwatchCompileReplace = scope.$watch( attrs.compileReplace, function (new_val) { if (new_val) { element.replaceWith($compile(new_val)(scope)); } } ); + + // Clean up watch on scope destroy + scope.$on('$destroy', function () { + if (unwatchCompileReplace) { + unwatchCompileReplace(); + unwatchCompileReplace = null; + } + }); } }; }] - ); \ No newline at end of file + ); diff --git a/src/directive/angular-tree-dnd-node.js b/src/directive/angular-tree-dnd-node.js index 318af8b..b40ed6e 100644 --- a/src/directive/angular-tree-dnd-node.js +++ b/src/directive/angular-tree-dnd-node.js @@ -84,10 +84,40 @@ angular.module('ntt.TreeDnD') objexpr = '[' + objprops.join(',') + ']'; - scope.$watch(objexpr, fnWatchNode, true); + var unwatchNode = scope.$watch(objexpr, fnWatchNode, true); scope.$on('$destroy', function () { + //removeIf(nodebug) + console.log('Destroyed Node'); + //endRemoveIf(nodebug) + + // Deregister the watch first + if (unwatchNode) { + unwatchNode(); + unwatchNode = null; + } + + // Remove from scope cache scope.deleteScope(scope, scope[keyNode]); + + // Remove from viewport + $TreeDnDViewport.remove(scope, element); + + // Clear the __inited__ flag on the node + if (scope[keyNode]) { + scope[keyNode].__inited__ = false; + } + + // Clear element reference to allow DOM garbage collection + scope.$element = null; + + // Clear cached child element reference + childsElem = null; + + // Clear function references that may hold closures + scope.getData = null; + scope.getElementChilds = null; + scope.getScopeNode = null; }); function fnWatchNode(newVal, oldVal, scope) { @@ -107,6 +137,7 @@ angular.module('ntt.TreeDnD') parentNode = scope.tree_nodes[parentReal] || null, _childs = nodeOf.__children__, _len = _childs.length, + _hasChilds = _len > 0 || nodeOf.__has_children__ === true || nodeOf.__lazy__ === true, _i; if (!nodeOf.__inited__) { @@ -130,7 +161,7 @@ angular.module('ntt.TreeDnD') nodeOf.__visible__ = true; } - if (_len === 0) { + if (!_hasChilds) { _icon = -1; } else { if (nodeOf.__expanded__) { @@ -169,4 +200,4 @@ angular.module('ntt.TreeDnD') } } }] - ); \ No newline at end of file + ); diff --git a/src/directive/angular-tree-dnd.js b/src/directive/angular-tree-dnd.js index 43b5a44..bbede4b 100644 --- a/src/directive/angular-tree-dnd.js +++ b/src/directive/angular-tree-dnd.js @@ -3,11 +3,11 @@ angular.module('ntt.TreeDnD') 'treeDnd', fnInitTreeDnD); fnInitTreeDnD.$inject = [ - '$timeout', '$http', '$compile', '$parse', '$window', '$document', '$templateCache', + '$timeout', '$http', '$compile', '$parse', '$window', '$document', '$templateCache', '$q', '$TreeDnDTemplate', '$TreeDnDClass', '$TreeDnDHelper', '$TreeDnDPlugin', '$TreeDnDViewport' ]; -function fnInitTreeDnD($timeout, $http, $compile, $parse, $window, $document, $templateCache, +function fnInitTreeDnD($timeout, $http, $compile, $parse, $window, $document, $templateCache, $q, $TreeDnDTemplate, $TreeDnDClass, $TreeDnDHelper, $TreeDnDPlugin, $TreeDnDViewport) { return { restrict: 'E', @@ -122,8 +122,18 @@ function fnInitTreeDnD($timeout, $http, $compile, $parse, $window, $document, $t } if (passedExpand) { + if (node.__expanded__) { + node.__expanded__ = false; + return; + } + if (node.__children__.length > 0) { - node.__expanded__ = !node.__expanded__; + node.__expanded__ = true; + return; + } + + if (nodeHasChildren(node) && angular.isFunction($scope.$callbacks.loadChildren)) { + $scope.loadChildren(node); } } }; @@ -192,6 +202,9 @@ function fnInitTreeDnD($timeout, $http, $compile, $parse, $window, $document, $t this.for_all_descendants(_clone, this.changeKey); return _clone; }, + loadChildren: function () { + return null; + }, remove: function (node, parent, _this, delayReload) { var temp = parent.splice(node.__index__, 1)[0]; if (!delayReload) { @@ -202,6 +215,13 @@ function fnInitTreeDnD($timeout, $http, $compile, $parse, $window, $document, $t clearInfo: function (node) { delete node.__inited__; delete node.__visible__; + delete node.__icon__; + delete node.__icon_class__; + delete node.__level__; + delete node.__index__; + delete node.__index_real__; + delete node.__parent_real__; + delete node.__dept__; // always changed after call reload_data //delete node.__hashKey__; @@ -247,6 +267,38 @@ function fnInitTreeDnD($timeout, $http, $compile, $parse, $window, $document, $t return $scope; }; + function nodeHasChildren(node) { + return (angular.isArray(node.__children__) && node.__children__.length > 0) || + node.__has_children__ === true || + node.__lazy__ === true; + } + + $scope.loadChildren = function (node) { + if (!node || node.__loading__) { + return $q.when([]); + } + + node.__loading__ = true; + + return $q.when($scope.$callbacks.loadChildren(node)).then( + function (children) { + if (angular.isArray(children)) { + node.__children__ = children; + } else if (!angular.isArray(node.__children__)) { + node.__children__ = []; + } + + node.__lazy__ = false; + node.__has_children__ = node.__children__.length > 0; + node.__expanded__ = node.__children__.length > 0; + reload_data(); + return node.__children__; + } + ).finally(function () { + node.__loading__ = false; + }); + }; + if ($attrs.enableDrag || $attrs.enableDrop) { $scope.placeElm = null; // $scope.dragBorder = 30; @@ -525,6 +577,8 @@ function fnInitTreeDnD($timeout, $http, $compile, $parse, $window, $document, $t beginAnd: true }, tree, + // Array to store watch deregistration functions for cleanup + _watchDeregistrations = [], _watches = [ [ 'enableDrag', @@ -698,7 +752,8 @@ function fnInitTreeDnD($timeout, $http, $compile, $parse, $window, $document, $t } if ($attrs.treeData) { - $scope.$watch( + // Store deregistration function for treeData watch + var unwatchTreeData = $scope.$watch( $attrs.treeData, function (val) { if (angular.equals(val, $scope.treeData)) { return; @@ -710,8 +765,70 @@ function fnInitTreeDnD($timeout, $http, $compile, $parse, $window, $document, $t } }, true ); + _watchDeregistrations.push(unwatchTreeData); } + $scope.$on('$destroy', function () { + // Cancel any pending timeouts + if (timeReloadData) { + $timeout.cancel(timeReloadData); + timeReloadData = null; + } + tmpTreeData = null; + + // Deregister all watches to prevent memory leaks + var i, len; + for (i = 0, len = _watchDeregistrations.length; i < len; i++) { + if (_watchDeregistrations[i]) { + _watchDeregistrations[i](); + } + } + _watchDeregistrations.length = 0; + + // Clear all scope references in $globals + if ($scope.$globals) { + var keys = Object.keys($scope.$globals); + for (i = 0, len = keys.length; i < len; i++) { + delete $scope.$globals[keys[i]]; + } + $scope.$globals = null; + } + + // Remove and clean up placeholder element + if ($scope.placeElm) { + $scope.placeElm.remove(); + $scope.placeElm = null; + } + + // Remove and clean up status element + if ($scope.statusElm) { + $scope.statusElm.remove(); + $scope.statusElm = null; + } + + // Clear tree node references + if ($scope.tree_nodes) { + $scope.tree_nodes.length = 0; + $scope.tree_nodes = null; + } + + // Clear treeData references + if ($scope.treeData) { + $scope.treeData = null; + } + + // Clear callbacks to break circular references + $scope.$callbacks = null; + + // Clear column definitions + $scope.colDefinitions = null; + + // Clear tree control reference + if (tree) { + tree = null; + } + }); + function timeLoadData() { $scope.treeData = tmpTreeData; reload_data(); @@ -769,7 +886,8 @@ function fnInitTreeDnD($timeout, $http, $compile, $parse, $window, $document, $t return;//jmp } if (typeof $attrs[nameAttr] === 'string') { - $scope.$watch( + // Store deregistration function for cleanup + var unwatchFn = $scope.$watch( $attrs[nameAttr], function (val) { if (typeof type === 'string' && typeof val === type || angular.isArray(type) && type.indexOf(typeof val) > -1 @@ -788,6 +906,7 @@ function fnInitTreeDnD($timeout, $http, $compile, $parse, $window, $document, $t } }, true ); + _watchDeregistrations.push(unwatchFn); } else { if (angular.isFunction(fnNotExist)) { @@ -868,11 +987,13 @@ function fnInitTreeDnD($timeout, $http, $compile, $parse, $window, $document, $t node.__parent__ = parent; _len = node.__children__.length; - if (angular.isUndefinedOrNull(node.__expanded__) && _len > 0) { + var _hasChildren = _len > 0 || node.__has_children__ === true || node.__lazy__ === true; + + if (angular.isUndefinedOrNull(node.__expanded__) && _hasChildren) { node.__expanded__ = level < $scope.expandLevel; } - if (_len === 0) { + if (!_hasChildren) { _icon = -1; } else { if (node.__expanded__) { @@ -926,9 +1047,18 @@ function fnInitTreeDnD($timeout, $http, $compile, $parse, $window, $document, $t function init_data(data) { - // clear memory - if (angular.isDefined($scope.tree_nodes)) { - delete $scope.tree_nodes; + // clear memory - properly clean up old nodes to prevent memory leaks + if (angular.isDefined($scope.tree_nodes) && $scope.tree_nodes) { + // Clear internal properties that may hold references + var i, len, node; + for (i = 0, len = $scope.tree_nodes.length; i < len; i++) { + node = $scope.tree_nodes[i]; + if (node) { + // Clear the __inited__ flag that creates circular references + delete node.__inited__; + } + } + $scope.tree_nodes.length = 0; } $scope.tree_nodes = data; diff --git a/src/factory/angular-tree-dnd-viewport.js b/src/factory/angular-tree-dnd-viewport.js index c0d1b8a..b342d77 100644 --- a/src/factory/angular-tree-dnd-viewport.js +++ b/src/factory/angular-tree-dnd-viewport.js @@ -14,20 +14,59 @@ function fnInitTreeDnDViewport($window, $document, $timeout, $q, $compile) { nodeTemplate, updateTimeout, renderTime, + windowListenersBound = false, $initViewport = { setViewport: setViewport, getViewport: getViewport, add: add, + remove: remove, setTemplate: setTemplate, getItems: getItems, - updateDelayed: updateDelayed + updateDelayed: updateDelayed, + destroy: destroy }, eWindow = angular.element($window); - eWindow.on('load resize scroll', updateDelayed); - return $initViewport; + /** + * Bind window event listeners (lazily on first add) + */ + function bindWindowListeners() { + if (!windowListenersBound) { + eWindow.on('load resize scroll', updateDelayed); + windowListenersBound = true; + } + } + + /** + * Unbind window event listeners and clean up resources + */ + function destroy() { + if (windowListenersBound) { + eWindow.off('load resize scroll', updateDelayed); + windowListenersBound = false; + } + + // Cancel any pending timeouts + if (updateTimeout) { + $timeout.cancel(updateTimeout); + updateTimeout = null; + } + if (renderTime) { + $timeout.cancel(renderTime); + renderTime = null; + } + + // Clear all item references + items.length = 0; + viewport = null; + nodeTemplate = null; + isUpdating = false; + isRender = false; + updateAgain = false; + } + function update() { viewportRect = { @@ -125,6 +164,8 @@ function fnInitTreeDnDViewport($window, $document, $timeout, $q, $compile) { * @param callback */ function add(scope, element) { + // Lazily bind window listeners on first add + bindWindowListeners(); updateDelayed(); items.push({ element: element, @@ -132,6 +173,23 @@ function fnInitTreeDnDViewport($window, $document, $timeout, $q, $compile) { }); } + function remove(scope, element) { + var i = items.length; + while (i--) { + if (items[i].scope === scope || (element && items[i].element === element)) { + // Clear references before removing + items[i].scope = null; + items[i].element = null; + items.splice(i, 1); + } + } + // Auto-cleanup: unbind window listeners when no items remain + if (items.length === 0 && windowListenersBound) { + eWindow.off('load resize scroll', updateDelayed); + windowListenersBound = false; + } + } + function setTemplate(scope, template) { nodeTemplate = template; } @@ -143,4 +201,4 @@ function fnInitTreeDnDViewport($window, $document, $timeout, $q, $compile) { function getItems() { return items; } -} \ No newline at end of file +} diff --git a/src/module/angular-tree-dnd-template.append.js b/src/module/angular-tree-dnd-template.append.js index 47d6ef9..8c86d90 100644 --- a/src/module/angular-tree-dnd-template.append.js +++ b/src/module/angular-tree-dnd-template.append.js @@ -14,8 +14,8 @@ angular.module('template/TreeDnD/TreeDnD.html', []).run( ' ', ' ', ' ', - ' ', '  Moving' ); }] -); \ No newline at end of file +); diff --git a/src/plugin/angular-tree-dnd-drag.js b/src/plugin/angular-tree-dnd-drag.js index d6b40cc..ec12af0 100644 --- a/src/plugin/angular-tree-dnd-drag.js +++ b/src/plugin/angular-tree-dnd-drag.js @@ -580,6 +580,11 @@ angular.module('ntt.TreeDnD') function _fnDragEnd(e, $params) { e.preventDefault(); + + // Always unbind document listeners first to prevent leaks + // even if dragElm is null (edge case handling) + _fnUnbindDocumentListeners($params); + if ($params.dragElm) { var _passed = false, _$scope = $params.$scope, @@ -644,17 +649,27 @@ angular.module('ntt.TreeDnD') } function clearData() { - $params.dragInfo.target.hidePlace(); - $params.dragInfo.target.targeting = false; + if ($params.dragInfo && $params.dragInfo.target) { + $params.dragInfo.target.hidePlace(); + $params.dragInfo.target.targeting = false; + } $params.dragInfo = null; - _$scope.$$apply = false; - _$scope.setDragging(null); + if ($params.$scope) { + $params.$scope.$$apply = false; + $params.$scope.setDragging(null); + } } + } - angular.element($params.$document).unbind('touchend', $params.dragEndEvent); // Mobile - angular.element($params.$document).unbind('touchcancel', $params.dragEndEvent); // Mobile - angular.element($params.$document).unbind('touchmove', $params.dragMoveEvent); // Mobile + /** + * Unbind all document-level event listeners + * Separated into its own function to ensure proper cleanup + */ + function _fnUnbindDocumentListeners($params) { + angular.element($params.$document).unbind('touchend', $params.dragEndEvent); + angular.element($params.$document).unbind('touchcancel', $params.dragEndEvent); + angular.element($params.$document).unbind('touchmove', $params.dragMoveEvent); angular.element($params.$document).unbind('mouseup', $params.dragEndEvent); angular.element($params.$document).unbind('mousemove', $params.dragMoveEvent); angular.element($params.$window.document.body).unbind('mouseleave', $params.dragCancelEvent); @@ -825,15 +840,49 @@ angular.module('ntt.TreeDnD') //unbind handler that retains scope scope.$on( '$destroy', function () { + // Unbind keyboard handlers angular.element($window.document.body).unbind('keydown', keydownHandler); angular.element($window.document.body).unbind('keyup', keyupHandler); + + // Unbind element drag listeners + $params.element.unbind('touchstart mousedown'); + $params.element.unbind('touchend touchcancel mouseup'); + + // Unbind any active document listeners (in case drag is in progress) + _fnUnbindDocumentListeners($params); + + // Cancel any pending drag timer + if ($params.dragTimer) { + $timeout.cancel($params.dragTimer); + $params.dragTimer = null; + } + + // Remove and clean up drag element if still present + if ($params.dragElm) { + $params.dragElm.remove(); + $params.dragElm = null; + } + + // Clean up status element if (scope.statusElm) { scope.statusElm.remove(); + scope.statusElm = null; } + // Clean up placeholder element if (scope.placeElm) { scope.placeElm.remove(); + scope.placeElm = null; } + + // Clear dragInfo to break circular references + $params.dragInfo = null; + $params.pos = null; + $params.placeElm = null; + + // Clear $params scope reference + $params.$scope = null; + $params.element = null; } ); }