',
' | 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(
' |
',
' ',
'