/**
* The MIT License (MIT)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
/**
* Implementing TreeDnD & Event DrapnDrop (allow drag multi tree-table include all type: table, ol, ul)
* Demo: http://thienhung1989.github.io/angular-tree-dnd
* Github: https://github.com/thienhung1989/angular-tree-dnd
* @version 3.0.10
* (c) 2015 Nguyuễn Thiện Hùng - <nguyenthienhung1989@gmail.com>
* @license
* The MIT License (MIT)
*
* Copyright (c) 2015 Nguyễn Thiện Hùng
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
(function () {
'use strict';
/**
* @namespace angular
*/
/**
* Is undefined or null
* @param {*} val - Value
* @returns {boolean}
*/
angular.isUndefinedOrNull = function isUndefinedOrNull(val) {
return angular.isUndefined(val) || val === null;
};
/**
* Is defined
*
* @param {*} val - Value
* @returns {boolean}
*/
angular.isDefined = function isDefined(val) {
return !(angular.isUndefined(val) || val === null);
};
/**
* @namespace Factory
* @type object
*/
/**
* @constant $TreeDnDClass
* @type object
* @default
* @property {string} [tree=tree-dnd] - Class tree
* @property {string} [empty=tree-dnd-empty] - Class tree empty
* @property {string} [hidden=tree-dnd-hidden] - Class tree hidden
* @property {string} [node=tree-dnd-node] - Class tree node
* @property {string} [nodes=tree-dnd-nodes] - Class tree nodes
* @property {string} [handle=tree-dnd-handle] - Class tree handle
* @property {string} [place=tree-dnd-place] - Class tree place
* @property {string} [drag=tree-dnd-drag] - Class tree drag
* @property {string} [status=tree-dnd-status] - Class tree status (coping, moving)
* @property {object} icon
*/
angular.module('ntt.TreeDnD', ['template/TreeDnD/TreeDnD.html'])
.constant('$TreeDnDClass', {
tree: 'tree-dnd',
empty: 'tree-dnd-empty',
hidden: 'tree-dnd-hidden',
node: 'tree-dnd-node',
nodes: 'tree-dnd-nodes',
handle: 'tree-dnd-handle',
place: 'tree-dnd-placeholder',
drag: 'tree-dnd-drag',
status: 'tree-dnd-status',
icon: {
'1': 'glyphicon glyphicon-minus',
'0': 'glyphicon glyphicon-plus',
'-1': 'glyphicon glyphicon-file'
}
});angular.module('ntt.TreeDnD')
.controller('treeDndNodeHandleController', [
'$scope',
function ($scope) {
this.scope = $scope;
}
]);
angular.module('ntt.TreeDnD')
.controller('treeDndNodeController', [
'$scope',
function ($scope) {
this.scope = $scope;
}
]);
angular.module('ntt.TreeDnD')
.controller('treeDndNodesController', [
'$scope',
function ($scope) {
this.scope = $scope;
}
]);
angular.module('ntt.TreeDnD')
.directive('compile', [
'$compile',
function ($compile) {
return {
restrict: 'A',
link: function (scope, element, attrs) {
scope.$watch(
attrs.compile,
function (new_val) {
if (new_val) {
if (angular.isFunction(element.empty)) {
element.empty();
} else {
element.html('');
}
element.append($compile(new_val)(scope));
}
}
);
}
};
}]
)
.directive('compileReplace', [
'$compile',
function ($compile) {
return {
restrict: 'A',
link: function (scope, element, attrs) {
scope.$watch(
attrs.compileReplace,
function (new_val) {
if (new_val) {
element.replaceWith($compile(new_val)(scope));
}
}
);
}
};
}]
);
angular.module('ntt.TreeDnD')
.directive('treeDndNodeHandle', function () {
return {
restrict: 'A',
scope: true,
controller: 'treeDndNodeHandleController',
link: fnLink
};
function fnLink(scope, element/*, attrs, controller*/) {
scope.$type = 'TreeDnDNodeHandle';
if (scope.$class.handle) {
element.addClass(scope.$class.handle);
}
}
});
angular.module('ntt.TreeDnD')
.directive('treeDndNode', [
'$TreeDnDViewport',
function ($TreeDnDViewport) {
return {
restrict: 'A',
replace: true,
controller: 'treeDndNodeController',
link: fnLink
};
/**
* Link
*
* @param {Object} scope
* @param {Object} element
* @param {Object} attrs
*
* @private
*/
function fnLink(scope, element, attrs) {
scope.$node_class = '';
if (scope.$class.node) {
element.addClass(scope.$class.node);
scope.$node_class = scope.$class.node;
}
var enabledDnD = typeof scope.dragEnabled === 'boolean' || typeof scope.dropEnabled === 'boolean',
keyNode = attrs.treeDndNode,
childsElem;
$TreeDnDViewport.add(scope, element);
if (enabledDnD) {
scope.$type = 'TreeDnDNode';
scope.getData = function () {
return scope[keyNode];
};
}
scope.$element = element;
scope[keyNode].__inited__ = true;
scope.getElementChilds = function () {
return angular.element(element[0].querySelector('[tree-dnd-nodes]'));
};
scope.setScope(scope, scope[keyNode]);
scope.getScopeNode = function () {
return scope;
};
var objprops = [],
objexpr,
i, keyO = Object.keys(scope[keyNode]),
lenO = keyO.length,
hashKey = scope[keyNode].__hashKey__,
skipAttr = [
'__visible__',
'__children__',
'__level__',
'__index__',
'__index_real__',
'__parent__',
'__parent_real__',
'__dept__',
'__icon__',
'__icon_class__'
],
keepAttr = [
'__expanded__'
],
lenKeep = keepAttr.length;
// skip __visible__
for (i = 0; i < lenO + lenKeep; i++) {
if (i < lenO) {
if (skipAttr.indexOf(keyO[i]) === -1) {
objprops.push(keyNode + '.' + keyO[i]);
}
} else {
if (keyO.indexOf(keepAttr[i - lenO]) === -1) {
objprops.push(keyNode + '.' + keepAttr[i - lenO]);
}
}
}
objexpr = '[' + objprops.join(',') + ']';
scope.$watch(objexpr, fnWatchNode, true);
scope.$on('$destroy', function () {
scope.deleteScope(scope, scope[keyNode]);
});
function fnWatchNode(newVal, oldVal, scope) {
var nodeOf = scope[keyNode];
if (typeof nodeOf !== 'object') {
return; // jmp out
}
if (!nodeOf.__inited__) {
nodeOf.__inited__ = true;
}
if (nodeOf.__hashKey__ !== hashKey) {
// clear scope in $globals
scope.deleteScope(scope, nodeOf);
// add new scope into $globals
scope.setScope(scope, nodeOf);
hashKey = nodeOf.__hashKey__;
}
var _childs = nodeOf.__children__,
_len = _childs.length,
_i;
var _icon;
if (_len === 0) {
_icon = -1;
} else {
if (nodeOf.__expanded__) {
_icon = 1;
} else {
_icon = 0;
}
}
nodeOf.__icon__ = _icon;
nodeOf.__icon_class__ = scope.$class.icon[_icon];
if (!scope.isTable) {
if (!childsElem) {
childsElem = scope.getElementChilds();
}
if (nodeOf.__expanded__) {
childsElem.removeClass(scope.$class.hidden);
} else {
childsElem.addClass(scope.$class.hidden);
}
}
for (_i = 0; _i < _len; _i++) {
scope.for_all_descendants(_childs[_i], scope.hiddenChild, nodeOf, true);
}
}
}
}]
);
angular.module('ntt.TreeDnD')
.directive('treeDndNodes', function () {
return {
restrict: 'A',
replace: true,
controller: 'treeDndNodesController',
link: fnLink
};
function fnLink(scope, element/*, attrs*/) {
scope.$type = 'TreeDnDNodes';
if (scope.$class.nodes) {
element.addClass(scope.$class.nodes);
scope.$nodes_class = scope.$class.nodes;
} else {
scope.$nodes_class = '';
}
}
});
angular.module('ntt.TreeDnD')
.directive('treeDnd', fnInitTreeDnD);
fnInitTreeDnD.$inject = [
'$timeout', '$http', '$compile', '$parse', '$window', '$document', '$templateCache',
'$TreeDnDTemplate', '$TreeDnDClass', '$TreeDnDHelper', '$TreeDnDPlugin', '$TreeDnDViewport'
];
function fnInitTreeDnD($timeout, $http, $compile, $parse, $window, $document, $templateCache,
$TreeDnDTemplate, $TreeDnDClass, $TreeDnDHelper, $TreeDnDPlugin, $TreeDnDViewport) {
return {
restrict: 'E',
scope: true,
replace: true,
controller: ['$scope', '$element', '$attrs', fnController],
compile: fnCompile
};
function fnController($scope, $element, $attrs) {
/**
* Scope of tree
* @namespace $scope
*/
/**
* Indent basic
*
* @type {number}
* @default 20
*/
$scope.indent = 20;
/**
* Indent plus each level
*
* @type {number}
* @default 15
*/
$scope.indent_plus = 15;
/**
* Indent unit
*
* @type {string}
* @default 'px'
*/
$scope.indent_unit = 'px';
/**
* Tree's class
*
* @type {string}
* @default 'table'
*/
$scope.$tree_class = 'table';
/**
* Primary key
*
* @type {string}
* @default '__uid__'
*/
$scope.primary_key = '__uid__';
/**
* Type of Tree
*
* @type {string}
* @default 'TreeDnD'
*/
$scope.$type = 'TreeDnD';
// $scope.enabledFilter = undefined;
$scope.colDefinitions = [];
$scope.$globals = {};
/**
* Classes status
* @type {Object}
*/
$scope.$class = angular.copy($TreeDnDClass);
angular.extend(
$scope.$class.icon, {
'1': $attrs.iconExpand || 'glyphicon glyphicon-minus',
'0': $attrs.iconCollapse || 'glyphicon glyphicon-plus',
'-1': $attrs.iconLeaf || 'glyphicon glyphicon-file'
}
);
/**
* Tree data
* @type {Node[]}
* @default []
*/
$scope.treeData = [];
/**
* Tree nodes
* @type {Node[]}
* @default []
*/
$scope.tree_nodes = [];
/**
* Function foreach all descendants
* @callback $scope.for_all_descendants
* @param {Node} node
* @param {Function|$scope.for_all_descendants} fn
* @param {Node} [parent]
* @param {boolean} [checkSibling=false] Check sibling of node
* @returns {boolean}
*/
$scope.for_all_descendants = function (node, fn, parent, checkSibling) {
if (angular.isFunction(fn)) {
var _i, _len, _nodes;
if (fn(node, parent)) {
// have error or need ignore children
return false;
}
if (typeof node !== 'object') {
return false;
}
_nodes = node.__children__;
_len = _nodes ? _nodes.length : 0;
for (_i = 0; _i < _len; _i++) {
if (!$scope.for_all_descendants(_nodes[_i], fn, node) && !checkSibling) {
// skip sibling of node checking
return false;
}
}
}
// succeed then continue
return true;
};
/**
* Get last descendant
*
* @param {Node|undefined} [node]
*
* @returns {Node|undefined}
*/
$scope.getLastDescendant = function (node) {
var last_child, n;
if (!node && typeof $scope.tree === 'object') {
node = $scope.tree.selected_node;
}
if (typeof node === 'object') {
if (angular.isArray(node.__children__)) {
n = node.__children__.length;
if (n === 0) {
return node;
} else {
last_child = node.__children__[n - 1];
return $scope.getLastDescendant(last_child);
}
}
}
};
/**
* Get element children
*
* @returns {Object}
*/
$scope.getElementChilds = function () {
return angular.element($element[0].querySelector('[tree-dnd-nodes]'));
};
/**
* Event onClick, will call function [on_click]{@link $scope.tree.on_click}
*
* @param {Node|undefined} node - For node
*/
$scope.onClick = function (node) {
if (angular.isDefined($scope.tree) && angular.isFunction($scope.tree.on_click)) {
// We want to detach from Angular's digest cycle so we can
// independently measure the time for one cycle.
setTimeout(
function () {
$scope.tree.on_click(node);
},
0
);
}
};
/**
* Event onSelect for node
*
* @param {Node|undefined} [node] - For node
*/
$scope.onSelect = function (node) {
if (angular.isDefined($scope.tree)) {
if (node !== $scope.tree.selected_node) {
$scope.tree.select_node(node);
}
if (angular.isFunction($scope.tree.on_select)) {
setTimeout(
function () {
$scope.tree.on_select(node);
},
0
);
}
}
};
/**
* Toggle Expand
*
* @param {Node|undefined} node - For node
* @param {Function} fnCallback
*/
$scope.toggleExpand = function (node, fnCallback) {
if (typeof node !== 'object') {
return; // jmp out
}
var passedExpand;
if (angular.isFunction(fnCallback)) {
passedExpand = !!fnCallback(node);
} else if (typeof $scope.$callbacks === 'object' && angular.isFunction($scope.$callbacks.expand)) {
passedExpand = !!$scope.$callbacks.expand(node);
}
// just for node has children
if (node.__children__.length > 0) {
if (typeof passedExpand !== 'undefined') {
node.__expanded__ = passedExpand;
} else {
node.__expanded__ = !node.__expanded__;
}
}
};
/**
* Get hash
* @callback _fnGetHash
*
* @param {Node} node
*
* @returns {string}
*
* @private
*/
var _fnGetHash = function (node) {
return '#' + node.__parent__ + '#' + node[$scope.primary_key];
},
/**
* Set hash
* @param {Node} node
* @returns {Node}
* @private
*/
_fnSetHash = function (node) {
var _hashKey = _fnGetHash(node);
if (angular.isUndefinedOrNull(node.__hashKey__) || node.__hashKey__ !== _hashKey) {
node.__hashKey__ = _hashKey;
}
return node;
};
/**
* Get hash of node
*
* @type {_fnGetHash}
*/
$scope.getHash = _fnGetHash;
/**
* Override callbacks
* @namespace $scope.$callbacks
* @type object
*/
$scope.$callbacks = {
getHash: _fnGetHash,
setHash: _fnSetHash,
for_all_descendants: $scope.for_all_descendants,
/*expand: function (node) {
return true;
},*/
accept: function (/*dragInfo, moveTo, isChanged*/) {
return $scope.dropEnabled === true;
},
/**
* Calc indent
*
* @param {int} level
* @param {boolean} skipUnit
* @param {boolean} skipEdge
* @returns {number|string}
*/
calsIndent: function (level, skipUnit, skipEdge) {
var unit = 0,
edge = skipEdge ? 0 : $scope.indent_plus;
if (!skipUnit) {
unit = $scope.indent_unit ? $scope.indent_unit : 'px';
}
if (level - 1 < 1) {
return edge + unit;
} else {
return $scope.indent * (level - 1) + edge + unit;
}
},
/**
* Is droppable
*
* @returns {boolean}
*/
droppable: function () {
return $scope.dropEnabled === true;
},
/**
* Is draggable
*
* @returns {boolean}
*/
draggable: function () {
return $scope.dragEnabled === true;
},
/**
* Before drop
*
* @returns {boolean}
*/
beforeDrop: function (/*event*/) {
return true;
},
/**
* Change key for node
*
* @param node
*/
changeKey: function (node) {
var _key = node.__uid__;
node.__uid__ = Math.random();
if (node.__selected__) {
delete node.__selected__;
}
if ($scope.primary_key !== '__uid__') {
_key = '' + node[$scope.primary_key];
_key = _key.replace(/_#.+$/g, '') + '_#' + node.__uid__;
node[$scope.primary_key] = _key;
}
// delete(node.__hashKey__);
},
/**
* Clone node
*
* @param node
* @returns {*}
*/
clone: function (node/*, _this*/) {
var _clone = angular.copy(node);
this.for_all_descendants(_clone, this.changeKey);
return _clone;
},
/**
* Remove node
*
* @param {Node} node
* @param {Node[]} parent
* @param {this} _this
* @param {boolean} delayReload
* @returns {Node[]}
*/
remove: function (node, parent, _this, delayReload) {
var temp = parent.splice(node.__index__, 1)[0];
if (!delayReload) {
$scope.reload_data();
}
return temp;
},
/**
* Clear info
*
* @param {Node} node
*/
clearInfo: function (node) {
delete node.__inited__;
delete node.__visible__;
// always changed after call reload_data
//delete node.__hashKey__;
},
/**
* Add node to
*
* @param {Node} node
* @param {int} pos
* @param {Node[]} parent
* @param {this} _this
*/
add: function (node, pos, parent/*, _this*/) {
// clearInfo
this.for_all_descendants(node, this.clearInfo);
if (parent) {
if (parent.length > -1) {
if (pos > -1) {
parent.splice(pos, 0, node);
} else {
// todo If children need load crazy
parent.push(node);
}
} else {
parent.push(node);
}
}
}
};
/**
* Delete scope by node
*
* @param {$scope} scope
* @param {Node} node
*/
$scope.deleteScope = function (scope, node) {
var _hash = node.__hashKey__;
if ($scope.$globals[_hash] && $scope.$globals[_hash] === scope) {
delete $scope.$globals[_hash];
}
};
/**
* Set scope for node
*
* @param {$scope} scope
* @param {Node} node
*/
$scope.setScope = function (scope, node) {
var _hash = node.__hashKey__;
if ($scope.$globals[_hash] !== scope) {
$scope.$globals[_hash] = scope;
}
};
/**
* Get scope of node
*
* @param {Node} node
* @returns {$scope}
*/
$scope.getScope = function (node) {
if (node) {
var _hash = node.__hashKey__;
//var _hash = typeof node === 'string' ? node : node.__hashKey__;
return $scope.$globals[_hash];
}
return $scope;
};
if ($attrs.enableDrag || $attrs.enableDrop) {
$scope.placeElm = undefined;
// $scope.dragBorder = 30;
$scope.dragEnabled = undefined;
$scope.dropEnabled = undefined;
$scope.horizontal = undefined;
if ($attrs.enableDrag) {
$scope.dragDelay = 0;
$scope.enabledMove = true;
$scope.statusMove = true;
$scope.enabledHotkey = false;
$scope.enabledCollapse = undefined;
$scope.statusElm = undefined;
$scope.dragging = undefined;
angular.extend(
$scope.$callbacks, /** @lends $scope.$callbacks */ {
beforeDrag: function (/*scopeDrag*/) {
return true;
},
/**
* Callback when drag stop
*
* @param info
* @param {boolean} passed
*/
dragStop: function (info, passed) {
if (!info || !info.changed && info.drag.enabledMove || !passed) {
return; // jmp out
}
info.target.reload_data();
if (info.target !== info.drag && info.drag.enabledMove) {
info.drag.reload_data();
}
},
/**
* Callback when node dropped
*
* @param info
* @returns {boolean}
*/
dropped: function (info/*, pass*/) {
if (!info) {
return; // jmp out
}
var _node = info.node,
_nodeAdd,
_move = info.move,
_parent,
_parentRemove = info.parent || info.drag.treeData,
_parentAdd = _move.parent || info.target.treeData,
isMove = info.drag.enabledMove;
if (!info.changed && isMove) {
return false;
}
if (info.target.$callbacks.accept(info, info.move, info.changed)) {
if (isMove) {
_parent = _parentRemove;
if (angular.isDefined(_parent.__children__)) {
_parent = _parent.__children__;
}
_nodeAdd = info.drag.$callbacks.remove(
_node,
_parent,
info.drag.$callbacks,
true // delay reload
);
} else {
_nodeAdd = info.drag.$callbacks.clone(_node, info.drag.$callbacks);
}
// if node dragging change index in sample node parent
// and index node decrement
if (isMove &&
info.drag === info.target &&
_parentRemove === _parentAdd &&
_move.pos >= info.node.__index__) {
_move.pos--;
}
_parent = _parentAdd;
if (_parent.__children__) {
_parent = _parent.__children__;
}
info.target.$callbacks.add(
_nodeAdd,
_move.pos,
_parent,
info.drag.$callbacks
);
return true;
}
return false;
},
/**
* Callback when before drag start
*
* @param event
*/
dragStart: function (event) {
},
/**
* Callback when before drag move
*
* @param event
*/
dragMove: function (event) {
}
}
);
/**
* Set status dragging
*
* @param dragInfo
*/
$scope.setDragging = function (dragInfo) {
$scope.dragging = dragInfo;
};
/**
* Get status node is enable move
*
* @param val
*/
$scope.enableMove = function (val) {
if (typeof val === 'boolean') {
$scope.enabledMove = val;
} else {
$scope.enabledMove = true;
}
};
if ($attrs.enableStatus) {
/**
* Enable status (moving, coping)
*
* @type {boolean}
*/
$scope.enabledStatus = false;
/**
* Hide status
*/
$scope.hideStatus = function () {
if ($scope.statusElm) {
$scope.statusElm.addClass($scope.$class.hidden);
}
};
/**
* Refresh Status
*/
$scope.refreshStatus = function () {
if (!$scope.dragging) {
return;
}
if ($scope.enabledStatus) {
var statusElmOld = $scope.statusElm;
if ($scope.enabledMove) {
$scope.statusElm = angular.element($TreeDnDTemplate.getMove($scope));
} else {
$scope.statusElm = angular.element($TreeDnDTemplate.getCopy($scope));
}
if (statusElmOld !== $scope.statusElm) {
if (statusElmOld) {
$scope.statusElm.attr('class', statusElmOld.attr('class'));
$scope.statusElm.attr('style', statusElmOld.attr('style'));
statusElmOld.remove();
}
$document.find('body').append($scope.statusElm);
}
$scope.statusElm.removeClass($scope.$class.hidden);
}
};
/**
* Set position status
*
* @param {Event} e
*/
$scope.setPositionStatus = function (e) {
if ($scope.statusElm) {
$scope.statusElm.css(
{
'left': e.pageX + 10 + 'px',
'top': e.pageY + 15 + 'px',
'z-index': 9999
}
);
$scope.statusElm.addClass($scope.$class.status);
}
};
}
}
/**
* Status targeting when drag & drop
*
* @type {boolean}
*/
$scope.targeting = false;
/**
* Get node previous sibling
*
* @param node
* @returns {Node|undefined}
*/
$scope.getPrevSibling = function (node) {
if (node && node.__index__ > 0) {
var _parent, _index = node.__index__ - 1;
if (angular.isDefined(node.__parent_real__)) {
_parent = $scope.tree_nodes[node.__parent_real__];
return _parent.__children__[_index];
}
return $scope.treeData[_index];
}
};
/**
* Get node by index
*
* @param {int} index
* @returns {Node|undefined}
*/
$scope.getNode = function (index) {
if (angular.isUndefinedOrNull(index)) {
return; // jmp out
}
return $scope.tree_nodes[index];
};
/**
* Init element place
*
* @param {DOMElement} element
* @param {DOMElement} dragElm
* @returns {*}
*/
$scope.initPlace = function (element, dragElm) {
if (!$scope.placeElm) {
if ($scope.isTable) {
$scope.placeElm = angular.element($window.document.createElement('tr'));
var _len_down = $scope.colDefinitions.length;
$scope.placeElm.append(
angular.element($window.document.createElement('td'))
.addClass($scope.$class.empty)
.addClass('indented')
.addClass($scope.$class.place)
);
while (_len_down-- > 0) {
$scope.placeElm.append(
angular.element($window.document.createElement('td'))
.addClass($scope.$class.empty)
.addClass($scope.$class.place)
);
}
} else {
$scope.placeElm = angular.element($window.document.createElement('li'))
.addClass($scope.$class.empty)
.addClass($scope.$class.place);
}
}
if (dragElm) {
$scope.placeElm.css('height', $TreeDnDHelper.height(dragElm) + 'px');
}
if (element) {
element[0].parentNode.insertBefore($scope.placeElm[0], element[0]);
} else {
$scope.getElementChilds().append($scope.placeElm);
}
return $scope.placeElm;
};
/**
* Hide element place
*/
$scope.hidePlace = function () {
if ($scope.placeElm) {
$scope.placeElm.addClass($scope.$class.hidden);
}
};
/**
* Show element place
*/
$scope.showPlace = function () {
if ($scope.placeElm) {
$scope.placeElm.removeClass($scope.$class.hidden);
}
};
/**
* Get scope tree
* @returns {$scope}
*/
$scope.getScopeTree = function () {
return $scope;
};
}
/**
* Function safe apply to avoid loop-depth
*
* @type {$safeApply}
*/
$scope.$safeApply = $safeApply;
/**
* Hide children
*
* @param {Node} node
* @param {Node} parent
* @returns {boolean}
*/
$scope.hiddenChild = function fnHiddenChild(node, parent) {
var nodeScope = $scope.getScope(node);
if (nodeScope) {
if (parent && parent.__expanded__ && parent.__visible__) {
nodeScope.$element.removeClass($scope.$class.hidden);
node.__visible__ = true;
} else {
nodeScope.$element.addClass($scope.$class.hidden);
node.__visible__ = false;
}
} else {
// show node & init scope
node.__visible__ = !!(parent && parent.__expanded__ && parent.__visible__);
}
// skip all child hiding... if not expaned
return node.__expanded__ === false;
};
var _fnInitFilter,
_fnInitOrderBy,
_fnGetControl,
_defaultFilterOption = {
showParent: true,
showChild: false,
beginAnd: true
},
_watches = [
[
'enableDrag',
[
['boolean', 'enableStatus', undefined, 'enabledStatus'],
['boolean', 'enableMove', undefined, 'enabledMove'],
['number', 'dragDelay', 0, undefined, 0],
['boolean', 'enableCollapse', undefined, 'enabledCollapse'],
['boolean', 'enableHotkey', undefined, 'enabledHotkey', undefined, function (isHotkey) {
if (isHotkey) {
$scope.enabledMove = false;
} else {
$scope.enabledMove = $scope.statusMove;
}
}]
]
],
[
['enableDrag', 'enableStatus'],
[
['string', 'templateCopy', $attrs.templateCopy, 'templateCopy', undefined, function (_url) {
if (_url && $templateCache.get(_url)) {
$TreeDnDTemplate.setCopy(_url, $scope);
}
}],
['string', 'templateMove', $attrs.templateMove, 'templateMove', undefined, function (_url) {
if (_url && $templateCache.get(_url)) {
$TreeDnDTemplate.setMove(_url, $scope);
}
}]
]
],
[
[['enableDrag', 'enableDrop']],
[
['number', 'dragBorder', 30, 'dragBorder', 30]
]
],
[
'*',
[
['boolean', 'treeTable', true, 'treeTable', undefined],
['boolean', 'horizontal'],
[
'callback',
'treeClass',
function (val) {
switch (typeof val) {
case 'string':
$scope.$tree_class = val;
break;
case 'object':
angular.extend($scope.$class, val);
$scope.$tree_class = $scope.$class.tree;
break;
default:
$scope.$tree_class = $attrs.treeClass;
break;
}
},
'treeClass',
function () {
$scope.$tree_class = $scope.$class.tree + ' table';
},
undefined,
function () {
if (/^(\s+[\w\-]+){2,}$/g.test(' ' + $attrs.treeClass)) {
$scope.$tree_class = $attrs.treeClass.trim();
return true;
}
}
],
[['object', 'string'], 'expandOn', getExpandOn, 'expandingProperty', getExpandOn, function (expandOn) {
if (angular.isUndefinedOrNull(expandOn)) {
$scope.expandingProperty = $attrs.expandOn;
}
}],
['object', 'treeControl', angular.isDefined($scope.tree) ? $scope.tree : {}, 'tree', undefined, function (treeControl) {
if (!angular.isFunction(_fnGetControl)) {
_fnGetControl = $TreeDnDPlugin('$TreeDnDControl');
}
if (angular.isFunction(_fnGetControl)) {
angular.extend(
$scope.tree,
_fnGetControl($scope),
treeControl
);
}
}],
[['array', 'object'], 'columnDefs', getColDefs, 'colDefinitions', getColDefs, function (colDefs) {
if (angular.isUndefinedOrNull(colDefs) || !angular.isArray(colDefs)) {
$scope.colDefinitions = getColDefs();
}
}],
[['object', 'string', 'array', 'function'], 'orderBy', $attrs.orderBy],
[['object', 'array'], 'filter', undefined, 'filter', undefined, function (filters) {
var _passed = false;
if (angular.isDefined(filters) && !angular.isArray(filters)) {
var _keysF = Object.keys(filters),
_lenF = _keysF.length, _iF;
if (_lenF > 0) {
for (_iF = 0; _iF < _lenF; _iF++) {
if (typeof filters[_keysF[_iF]] === 'string' &&
filters[_keysF[_iF]].length === 0) {
continue;
}
_passed = true;
break;
}
}
}
$scope.enabledFilter = _passed;
reload_data();
}],
['object', 'filterOptions', _defaultFilterOption, 'filterOptions', _defaultFilterOption, function (option) {
if (typeof option === 'object') {
$scope.filterOptions = angular.extend(_defaultFilterOption, option);
}
}],
['string', 'primaryKey', $attrs.primaryKey, 'primary_key', '__uid__'],
['string', 'indentUnit', $attrs.indentUnit, 'indent_unit'],
['number', 'indent', 30, 'indent', 30],
['number', 'indentPlus', 20, 'indent_plus', 20],
[
'object',
'callbacks',
function (optCallbacks) {
angular.forEach(
optCallbacks, function (value, key) {
if (typeof value === 'function') {
if ($scope.$callbacks[key]) {
$scope.$callbacks[key] = value;
}
}
}
);
return $scope.$callbacks;
},
'$callbacks'
],
['number', 'expandLevel', 3, 'expandLevel', 3, function () {
reload_data();
}],
['number', 'treeLimit', 100, '$TreeLimit', 100],
['boolean', 'enableDrag', undefined, 'dragEnabled'],
['boolean', 'enableDrop', undefined, 'dropEnabled']
]
]
],
w, lenW = _watches.length,
i, len,
_curW,
_typeW, _nameW, _defaultW, _scopeW, _NotW, _AfterW, _BeforeW,
// debounce reload_Data;
timeReloadData, tmpTreeData;
for (w = 0; w < lenW; w++) {
// skip if not exist
if (!check_exist_attr($attrs, _watches[w][0], true)) {
continue;
}
_curW = _watches[w][1];
for (i = 0, len = _curW.length; i < len; i++) {
_typeW = _curW[i][0];
_nameW = _curW[i][1];
_defaultW = _curW[i][2];
_scopeW = _curW[i][3];
_NotW = _curW[i][4];
_AfterW = _curW[i][5];
_BeforeW = _curW[i][6];
generateWatch(_typeW, _nameW, _defaultW, _scopeW, _NotW, _AfterW, _BeforeW);
}
}
if ($attrs.treeData) {
$scope.$watch(
$attrs.treeData, function (val) {
if (angular.equals(val, $scope.treeData)) {
return; // jmp out
}
tmpTreeData = val;
if (angular.isUndefinedOrNull(timeReloadData)) {
timeReloadData = $timeout(timeLoadData, 350);
}
}, true
);
}
/**
* Reload data with timeout
* @callback timeLoadData
*/
function timeLoadData() {
$scope.treeData = tmpTreeData;
reload_data();
timeReloadData = undefined;
}
/**
* Update limit
*/
$scope.updateLimit = function updateLimit() {
$scope.$TreeLimit += 50;
};
/**
* Reload data
* @type {reload_data}
*/
$scope.reload_data = reload_data;
/**
* Check attribute exist
* @callback check_exist_attr
*
* @param {object|array} attrs - Array attributes
* @param {Array|string} existAttr - Criteria condition
* @param {boolean} isAnd - Is condition AND
* @returns {*}
*/
function check_exist_attr(attrs, existAttr, isAnd) {
if (angular.isUndefinedOrNull(existAttr)) {
return false;
}
if (existAttr === '*' || !angular.isUndefined(attrs[existAttr])) {
return true;
}
if (angular.isArray(existAttr)) {
return for_each_attrs(attrs, existAttr, isAnd);
}
}
/**
* Foreach attributes with criteria
* @callback for_each_attrs
* @param {Object|Array} attrs - Array attributes
* @param {Array|string} exist - Criteria condition
* @param {boolean} isAnd - Is condition AND
* @returns {boolean}
*/
function for_each_attrs(attrs, exist, isAnd) {
var i, len = exist.length, passed = false;
if (len === 0) {
return; // jmp out
}
for (i = 0; i < len; i++) {
if (check_exist_attr(attrs, exist[i], !isAnd)) {
passed = true;
if (!isAnd) {
return true;
}
} else {
if (isAnd) {
return false;
}
}
}
return passed;
}
/**
* Function generate watch attribute by automatic
*
* @callback generateWatch
* @param {*} type
* @param {string} nameAttr - Name attribute
* @param {*} valDefault - Value default
* @param {string|undefined} nameScope - Name of attribute in $scope
* @param {function} fnNotExist - Callback when attribute not exist
* @param {function} fnAfter - Callback when attribute found
* @param {function} fnBefore - Callback before attribute found (to prepare data)
*/
function generateWatch(type, nameAttr, valDefault, nameScope, fnNotExist, fnAfter, fnBefore) {
nameScope = nameScope || nameAttr;
if (typeof type === 'string' || angular.isArray(type)) {
if (angular.isFunction(fnBefore) && fnBefore()) {
return;//jmp
}
if (typeof $attrs[nameAttr] === 'string') {
$scope.$watch(
$attrs[nameAttr], function (val) {
if (typeof type === 'string' && typeof val === type ||
angular.isArray(type) && type.indexOf(typeof val) > -1
) {
$scope[nameScope] = val;
} else {
if (angular.isFunction(valDefault)) {
$scope[nameScope] = valDefault(val);
} else {
$scope[nameScope] = valDefault;
}
}
if (angular.isFunction(fnAfter)) {
fnAfter($scope[nameScope], $scope);
}
}, true
);
} else {
if (angular.isFunction(fnNotExist)) {
$scope[nameScope] = fnNotExist();
} else if (!angular.isUndefined(fnNotExist)) {
$scope[nameScope] = fnNotExist;
}
}
}
}
/**
* Call safeApply
*
* @param fn
* @callback $safeApply
*/
function $safeApply(fn) {
var phase = this.$root.$$phase;
if (phase === '$apply' || phase === '$digest') {
if (fn && typeof fn === 'function') {
fn();
}
} else {
this.$apply(fn);
}
}
/**
* Get Expand on
* @callback getExpandOn
*/
function getExpandOn() {
if ($scope.treeData && $scope.treeData.length) {
var _firstNode = $scope.treeData[0], _keys = Object.keys(_firstNode),
_regex = new RegExp('^__([a-zA-Z0-9_\-]*)__$'),
_len,
i;
// Auto get first field with type is string;
for (i = 0, _len = _keys.length; i < _len; i++) {
if (typeof _firstNode[_keys[i]] === 'string' && !_regex.test(_keys[i])) {
$scope.expandingProperty = _keys[i];
return; // jmp out
}
}
// Auto get first
if (angular.isUndefinedOrNull($scope.expandingProperty)) {
$scope.expandingProperty = _keys[0];
}
}
}
/**
* Get col defs
*
* @callback getColDefs
*/
function getColDefs() {
// Auto get Defs except attribute __level__ ....
if ($scope.treeData.length) {
var _col_defs = [], _firstNode = $scope.treeData[0],
_regex = new RegExp('(^__([a-zA-Z0-9_\-]*)__$|^' + $scope.expandingProperty + '$)'),
_keys = Object.keys(_firstNode),
i, _len;
// Auto get first field with type is string;
for (i = 0, _len = _keys.length; i < _len; i++) {
if (typeof _firstNode[_keys[i]] === 'string' && !_regex.test(_keys[i])) {
_col_defs.push(
{
field: _keys[i]
}
);
}
}
$scope.colDefinitions = _col_defs;
}
}
/**
* do_f
*
* @callback do_f
*
* @param {Node[]} root
* @param {Node} node
* @param {Node} parent
* @param {int} parent_real
* @param {int} level
* @param {boolean|*} visible
* @param {int} index
* @returns {number}
*/
function do_f(root, node, parent, parent_real, level, visible, index) {
/**
* Node of tree
* @name Node
* @type {NodeBase}
* @property {int} __parent_real__
* @property {Node} __parent__
* @property {boolean} __expanded__
* @property {int} __index__
* @property {int} __index_real__
* @property {int} __level__
* @property {int} __icon__
* @property {string} __icon_class__
* @property {boolean} __visible__
* @property {string} __uid__
* @property {string} __hashKey__
* @property {int} __dept__
*/
if (typeof node !== 'object') {
return 0;
}
var _i, _len, _icon, _index_real, _dept, _hashKey;
if (!angular.isArray(node.__children__)) {
node.__children__ = [];
}
node.__parent_real__ = parent_real;
node.__parent__ = parent;
_len = node.__children__.length;
if (angular.isUndefinedOrNull(node.__expanded__) && _len > 0) {
node.__expanded__ = level < $scope.expandLevel;
}
if (_len === 0) {
_icon = -1;
} else {
if (node.__expanded__) {
_icon = 1;
} else {
_icon = 0;
}
}
// Insert item vertically
_index_real = root.length;
node.__index__ = index;
node.__index_real__ = _index_real;
node.__level__ = level;
node.__icon__ = _icon;
node.__icon_class__ = $scope.$class.icon[_icon];
node.__visible__ = !!visible;
if (angular.isUndefinedOrNull(node.__uid__)) {
node.__uid__ = '' + Math.random();
}
_hashKey = $scope.getHash(node);
if (angular.isUndefinedOrNull(node.__hashKey__) || node.__hashKey__ !== _hashKey) {
node.__hashKey__ = _hashKey;
}
root.push(node);
// Check node children
_dept = 1;
if (_len > 0) {
for (_i = 0; _i < _len; _i++) {
_dept += do_f(
root,
node.__children__[_i],
node[$scope.primary_key],
_index_real,
level + 1,
visible && node.__expanded__,
_i
);
}
}
node.__dept__ = _dept;
return _dept;
}
/**
* Init data for tree
*
* @param {Node[]|undefined} data - Data for tree
* @returns {Node[]|undefined}
*/
function init_data(data) {
// clear memory
if (angular.isDefined($scope.tree_nodes)) {
delete $scope.tree_nodes;
}
$scope.tree_nodes = data;
return data;
}
/**
* Reload data of tree
*
* @callback reload_data
*
* @param {Node[]|undefined} [data=undefined]
* @returns {Node[]}
*/
function reload_data(data) {
var _data,
_len,
_tree_nodes = [];
if (angular.isDefined(data)) {
if (!angular.isArray(data) || data.length === 0) {
return init_data([]);
} else {
_data = data;
}
} else if (!angular.isArray($scope.treeData) || $scope.treeData.length === 0) {
return init_data([]);
} else {
_data = $scope.treeData;
}
if (!$attrs.expandOn) {
getExpandOn();
}
if (!$attrs.columnDefs) {
getColDefs();
}
if (angular.isDefined($scope.orderBy)) {
if (!angular.isFunction(_fnInitOrderBy)) {
_fnInitOrderBy = $TreeDnDPlugin('$TreeDnDOrderBy');
}
if (angular.isFunction(_fnInitOrderBy)) {
_data = _fnInitOrderBy(_data, $scope.orderBy);
}
}
if (angular.isDefined($scope.filter)) {
if (!angular.isFunction(_fnInitFilter)) {
_fnInitFilter = $TreeDnDPlugin('$TreeDnDFilter');
}
if (angular.isFunction(_fnInitFilter)) {
_data = _fnInitFilter(_data, $scope.filter, $scope.filterOptions);
}
}
_len = _data.length;
if (_len > 0) {
var _i,
_deptTotal = 0;
for (_i = 0; _i < _len; _i++) {
_deptTotal += do_f(_tree_nodes, _data[_i], undefined, undefined, 1, true, _i);
}
}
init_data(_tree_nodes);
return _tree_nodes;
}
}
function fnCompile(tElement) {
var $_Template = '',
_element = tElement.html().trim();
if (_element.length > 0) {
$_Template = _element;
tElement.html('');
}
return function fnPost(scope, element, attrs) {
if (typeof attrs === 'object' && attrs.enableDrag) {
var _fnInitDrag = $TreeDnDPlugin('$TreeDnDDrag');
if (angular.isFunction(_fnInitDrag)) {
_fnInitDrag(scope, element, $window, $document);
}
}
// kick out $digest
element.ready(function () {
// apply Template
function checkTreeTable(template, scope) {
var elemNode = template[0].querySelector('[tree-dnd-node]'),
attrInclude;
scope.isTable = undefined;
if (elemNode) {
elemNode = angular.element(elemNode);
attrInclude = elemNode.attr('ng-include');
} else {
return;
}
if (attrInclude) {
var treeInclude = $parse(attrInclude)(scope) || attrInclude;
if (typeof treeInclude === 'string') {
return $http.get(
treeInclude,
{cache: $templateCache}
).then(function (response) {
var data = response.data || '';
data = data.trim();
//scope.templateNode = data;
var tempDiv = document.createElement('div');
tempDiv.innerHTML = data;
tempDiv = angular.element(tempDiv);
scope.isTable = !tempDiv[0].querySelector('[tree-dnd-nodes]');
}
);
}
} else {
scope.isTable = !elemNode[0].querySelector('[tree-dnd-nodes]');
//scope.templateNode = elemNode.html();
}
$TreeDnDViewport.setTemplate(scope, scope.templateNode);
//elemNode.html('');
}
var promiseCheck;
if ($_Template.length > 0) {
promiseCheck = checkTreeTable(angular.element($_Template.trim()), scope);
if (typeof promiseCheck === 'object') {
promiseCheck.then(function () {
element.append($compile($_Template)(scope));
});
} else {
element.append($compile($_Template)(scope));
}
} else {
$http.get(
attrs.templateUrl || $TreeDnDTemplate.getPath(),
{cache: $templateCache}
).then(function (response) {
var data = response.data || '';
data = angular.element(data.trim());
promiseCheck = checkTreeTable(data, scope);
if (typeof promiseCheck === 'object') {
promiseCheck.then(function () {
element.append($compile(data)(scope));
});
} else {
element.append($compile(data)(scope));
}
}
);
}
});
};
}
}
/**
* Factory $TreeDnDFilter
* @namespace $TreeDnDFilter
* @type function
* @function
*/
angular.module('ntt.TreeDnD')
.factory('$TreeDnDFilter', [
'$filter',
function ($filter) {
return fnInitFilter;
/**
* Foreach all descendants
*
* @param {array|object} options
* @param {Node} node
* @param {string} fieldChild
* @param {Function} [fnBefore] - Callback before foreach descendants of node
* @param {Function} [fnAfter] - Callback after foreach descendants of node
* @param {boolean} [parentPassed=false] - Parent is passed
* @returns {boolean|undefined}
* @callback for_all_descendants
* @private
*/
function for_all_descendants(options, node, fieldChild, fnBefore, fnAfter, parentPassed) {
if (!angular.isFunction(fnBefore)) {
return; // jmp out
}
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 {Node[]|Node|boolean|string|regex} data
* @returns {undefined|boolean}
* @callback _fnCheck
* @private
*/
function _fnCheck(callback, data) {
if (angular.isUndefinedOrNull(data) || angular.isArray(data)) {
return; // jmp out
}
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;
}
}
}
}
}
/**
* `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 {boolean} isAnd
* @returns {undefined|boolean}
* @private
*/
function _fnProcess(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]);
}
}
}
/**
*
* @param {Node} node
* @param {array} conditions - Array `conditions`
* @param {boolean} isAnd - Check with condition `And`, if `And` then `return false` when all `false`
* @returns {undefined|boolean}
*/
function for_each_filter(node, conditions, isAnd) {
var i, len = conditions.length || 0, passed = false;
if (len === 0) {
return; // jmp out
}
for (i = 0; i < len; i++) {
if (_fnProcess(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 {NodeFilter} node
* @param {boolean} isNodePassed
* @param {boolean} isChildPassed
* @param {boolean} isParentPassed
* @private
*/
function _fnAfter(options, node, isNodePassed, isChildPassed, isParentPassed) {
/**
* @name NodeFilter
* @extends Node
* @property {boolean} __filtered__
* @property {boolean} __filtered_visible__
* @property {int} __filtered_index__
*/
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 `_fnProcess` {@see _fnProcess}
*
* @param {object} options
* @param {NodeFilter} node
* @returns {undefined|boolean}
* @callback _fnBefore
* @private
*/
function _fnBefore(options, node) {
if (options.filter.length === 0) {
return true;
} else {
return _fnProcess(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 {NodeFilter} node
* @returns {undefined|boolean}
* @callback _fnBeforeClear
* @private
*/
function _fnBeforeClear(options, node) {
return false;
}
/**
* `_fnConvert` to convert `filter` `object` to `array` invaild.
*
* @param {object|array} filters
* @returns {array} Instead of `filter` or new array invaild *(converted from filter)*
* @callback _fnConvert
* @private
*/
function _fnConvert(filters) {
var _iF, _lenF, _keysF,
_filter,
_state;
// convert filter object to array filter
if (typeof filters === 'object' && !angular.isArray(filters)) {
_keysF = Object.keys(filters);
_lenF = _keysF.length;
_filter = [];
if (_lenF > 0) {
for (_iF = 0; _iF < _lenF; _iF++) {
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 (typeof filters[_keysF[_iF]] === 'object') {
_state = _fnConvert(filters[_keysF[_iF]]);
} else {
_state = {
field: _keysF[_iF],
callback: filters[_keysF[_iF]]
};
}
_filter.push(_state);
}
}
return _filter;
}
else {
return filters;
}
}
/**
* `fnInitFilter` function is constructor of service `$TreeDnDFilter`.
* @param {NodeFilter|NodeFilter[]} treeData
* @param {object|array} filters
* @param {object} options
* @param {string} keyChild
* @returns {array} Return `treeData` or `treeData` with `filter`
*/
function fnInitFilter(treeData, filters, options, keyChild) {
if (!angular.isArray(treeData)
|| treeData.length === 0) {
return treeData;
}
var _i, _len,
_filter;
_filter = _fnConvert(filters);
if (!(angular.isArray(_filter) || typeof _filter === 'object')
|| _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;
}
}]
);
/**
* Factory $TreeDnDOrderBy
*
* @name Factory.$TreeDnDOrderBy
* @type {fnInitTreeOrderBy}
*/
angular.module('ntt.TreeDnD')
.factory('$TreeDnDOrderBy', [
'$filter',
function ($filter) {
var _fnOrderBy = $filter('orderBy'),
/**
* Foreach all descendants
*
* @param options
* @param {Node} node - Node
* @param {string} name - Name attribute
* @param {function} fnOrderBy - Callback orderBy
* @returns {Node}
* @callback for_all_descendants
* @private
*/
for_all_descendants = function for_all_descendants(options, node, name, fnOrderBy) {
var _i, _len, _nodes;
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;
},
/**
* Function order
* @param {Node[]} list
* @param {string} orderBy
* @returns {Node[]}
* @private
*/
_fnOrder = function _fnOrder(list, orderBy) {
return _fnOrderBy(list, orderBy);
},
/**
* Function tree orderBy
*
* @type {function}
* @param {Node[]} treeData
* @param {string} orderBy
* @returns {Node[]}
* @callback fnInitTreeOrderBy
*/
fnInitTreeOrderBy = function fnInitTreeOrderBy(treeData, orderBy) {
if (!angular.isArray(treeData)
|| treeData.length === 0
|| !(angular.isArray(orderBy) || typeof orderBy === 'object' || angular.isString(orderBy) || angular.isFunction(orderBy))
|| orderBy.length === 0 && !angular.isFunction(orderBy)
) {
return treeData;
}
var _i, _len;
for (_i = 0, _len = treeData.length; _i < _len; _i++) {
treeData[_i] = for_all_descendants(
orderBy,
treeData[_i],
'__children__',
_fnOrder
);
}
return _fnOrder(treeData, orderBy);
};
return fnInitTreeOrderBy;
}]
);
/**
* Factory $TreeDnDConvert
*
* @name Factory.$TreeDnDConvert
* @type {$TreeDnDConvert}
*/
angular.module('ntt.TreeDnD')
.factory('$TreeDnDConvert', function () {
/**
* NodeBase
* @name NodeBase
* @type object
* @property {NodeBase[]|undefined} [__children__]
*/
/**
* @name $TreeDnDConvert
* @type object
* @default
*/
var $TreeDnDConvert = {
/**
* Line to tree
*
* @param {Array|Object} data
* @param {string} primaryKey
* @param {string} parentKey
* @param {function} callback
* @returns {NodeBase[]}
*/
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__) {
if (angular.isArray(parent.__children__)) {
parent.__children__.push(item);
} else {
console.error('Type of `parent.__children__` isn\'t array');
console.log(parent.__children__);
}
} else {
parent.__children__ = [item];
}
}
} else {
rootIds.push(_primary);
}
}
len = rootIds.length;
for (i = 0; i < len; i++) {
tree.push(treeObjs[rootIds[i]]);
}
return tree;
},
/**
* Convert tree to tree
*
* @param {array|object} data
* @param {string} containKey
* @param {function} callback
* @returns {NodeBase[]}
*/
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;
}
};
return $TreeDnDConvert;
});
/**
* Factory $TreeDnDHelper
* @namespace $TreeDnDHelper
* @name $TreeDnDHelper
*/
angular.module('ntt.TreeDnD')
.factory('$TreeDnDHelper', [
'$document', '$window',
function ($document, $window) {
var _$helper = /** @lends $TreeDnDHelper */ {
/**
* Status is no draggable
*
* @param {DOMElement} targetElm
* @returns {boolean}
*/
nodrag: function (targetElm) {
return typeof targetElm.attr('data-nodrag') !== 'undefined';
},
/**
*
* Get event's object
* @param {object} e
* @returns {object|null}
*/
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;
},
/**
* Get drag info
*
* @param {$scope} scope
* @returns {object}
*/
dragInfo: function (scope) {
var _node = scope.getData(),
_tree = scope.getScopeTree(),
_parent = scope.getNode(_node.__parent_real__);
return {
node: _node,
parent: _parent,
move: {
parent: _parent,
pos: _node.__index__
},
scope: scope,
target: _tree,
drag: _tree,
drop: scope.getPrevSibling(_node),
changed: false
};
},
/**
* Get element's height
*
* @param {DOMElement} element
* @returns {number}
*/
height: function (element) {
return element.prop('scrollHeight');
},
/**
* Get element's width
*
* @param {DOMElement} element
* @returns {number}
*/
width: function (element) {
return element.prop('scrollWidth');
},
/**
* Get element's offset
*
* @param {DOMElement} element
* @returns {{width: *, height: *, top: *, left: *}}
*/
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)
};
},
/**
* Get position started of element drag or drop
*
* @param {Event} e
* @param {DOMElement} target
* @returns {ElementPosition}
*/
positionStarted: function (e, target) {
/**
* Element position information (when drag & drop)
*
* @name ElementPosition
* @type {object}
* @property {number} offsetX
* @property {number} offsetY
* @property {number} startX
* @property {number} lastX
* @property {number} startY
* @property {number} lastY
* @property {number} nowX
* @property {number} nowY
* @property {number} distX - Distance of X
* @property {number} distY - Distance of Y
* @property {number} dirAX - Direct of Ax
* @property {number} dirX - Direct of X
* @property {number} dirY - Direct of Y
* @property {number} LastDirX - Last direct of X
* @property {number} distAxX - Distance of AxX
* @property {number} distAxY - Distance of AxY
*/
var ElementPosition = {
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
};
return ElementPosition;
},
/**
* Get position moved
*
* @param {Event} e
* @param {ElementPosition} pos
* @param {bool} firstMoving
* @return {object}
*/
positionMoved: function (e, pos, firstMoving) {
// mouse position last events
pos.lastX = pos.nowX;
pos.lastY = pos.nowY;
// mouse position this events
pos.nowX = e.pageX;
pos.nowY = e.pageY;
// distance mouse moved between events
pos.distX = pos.nowX - pos.lastX;
pos.distY = pos.nowY - pos.lastY;
// direction mouse was moving
pos.lastDirX = pos.dirX;
pos.lastDirY = pos.dirY;
// direction mouse is now moving (on both axis)
pos.dirX = pos.distX === 0 ? 0 : pos.distX > 0 ? 1 : -1;
pos.dirY = pos.distY === 0 ? 0 : pos.distY > 0 ? 1 : -1;
// axis mouse is now moving on
var newAx = Math.abs(pos.distX) > Math.abs(pos.distY) ? 1 : 0;
// do nothing on first move
if (firstMoving) {
pos.dirAx = newAx;
pos.moving = true;
return; // jmp out
}
// 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;
return pos;
},
/**
* Replace with indent
*
* @param {$scope} scope
* @param {DOMElement} element
* @param {number} indent
* @param {string} attr
*/
replaceIndent: function (scope, element, indent, attr) {
attr = attr || 'left';
angular.element(element.children()[0]).css(attr, scope.$callbacks.calsIndent(indent));
},
/**
* Is type tree node
*
* @param {DOMElement} element
* @returns {boolean}
*/
isTreeDndNode: function (element) {
if (element) {
var $element = angular.element(element);
return $element && $element.length && typeof $element.attr('tree-dnd-node') !== 'undefined';
}
return false;
},
/**
* Is tree nodes (container)
*
* @param {DOMElement} element
* @returns {boolean}
*/
isTreeDndNodes: function (element) {
if (element) {
var $element = angular.element(element);
return $element && $element.length && typeof $element.attr('tree-dnd-nodes') !== 'undefined';
}
return false;
},
/**
* Is tree node handle (element to call event drag)
*
* @param {DOMElement} element
* @returns {boolean}
*/
isTreeDndNodeHandle: function (element) {
if (element) {
var $element = angular.element(element);
return $element && $element.length && typeof $element.attr('tree-dnd-node-handle') !== 'undefined';
}
return false;
},
/**
* Is tree droppable
*
* @param {DOMElement} element
* @returns {boolean}
*/
isTreeDndDroppable: function (element) {
return _$helper.isTreeDndNode(element)
|| _$helper.isTreeDndNodes(element)
|| _$helper.isTreeDndNodeHandle(element);
},
/**
* Find element closest by attribute
*
* @param {DOMElement} element
* @param {string|function} attr
* @returns {DOMElement}
*/
closestByAttr: function fnClosestByAttr(element, attr) {
if (element && attr) {
var $element = angular.element(element),
$parent = $element.parent();
if ($parent) {
var isPassed = false;
switch (typeof attr) {
case 'function':
isPassed = attr($parent);
break;
default:
isPassed = typeof $parent.attr(attr) !== 'undefined';
break;
}
if (isPassed) {
return $parent;
} else {
return fnClosestByAttr($parent, attr);
}
}
}
}
};
return _$helper;
}]
);
angular.module('ntt.TreeDnD')
.factory('$TreeDnDPlugin', [
'$injector',
function ($injector) {
return _fnget;
function _fnget(name) {
if (angular.isDefined($injector) && $injector.has(name)) {
return $injector.get(name);
}
}
}]
);
/**
* Factory `$TreeDnDTemplate`
* @name Factory.$TreeDnDTemplate
* @type {TreeDnDTemplate}
*/
angular.module('ntt.TreeDnD')
.factory('$TreeDnDTemplate', [
'$templateCache',
function ($templateCache) {
var templatePath = 'template/TreeDnD/TreeDnD.html',
/**
* @private
* @type {string}
*/
copyPath = 'template/TreeDnD/TreeDnDStatusCopy.html',
/**
* @private
* @type {string}
*/
movePath = 'template/TreeDnD/TreeDnDStatusMove.html',
/**
* @private
* @type {object}
*/
scopes = {};
/**
* TreeDnDTemplate
*
* @constructor TreeDnDTemplate
* @hideConstructor
*/
var InitTreeDnDTemplate = /** @lends TreeDnDTemplate */ {
/**
* Set path of template move
*
* @param {string} path - Path of template
* @param {$scope} scope - Scope of tree
*/
setMove: function (path, scope) {
if (!scopes[scope.$id]) {
scopes[scope.$id] = {};
}
scopes[scope.$id].movePath = path;
},
/**
* Set path of template copy
*
* @param {string} path - Path of template
* @param {$scope} scope - Scope of tree
*/
setCopy: function (path, scope) {
if (!scopes[scope.$id]) {
scopes[scope.$id] = {};
}
scopes[scope.$id].copyPath = path;
},
/**
* Get template's path
*
* @returns {string}
*/
getPath: function () {
return templatePath;
},
/**
* Get template's copy
*
* @param {$scope} scope - Scope of tree
* @returns {string|html}
*/
getCopy: function (scope) {
if (scopes[scope.$id] && scopes[scope.$id].copyPath) {
var temp = $templateCache.get(scopes[scope.$id].copyPath);
if (temp) {
return temp;
}
}
return $templateCache.get(copyPath);
},
/**
* Get template's move
*
* @param {$scope} scope - Scope of tree
* @returns {string|html}
*/
getMove: function (scope) {
if (scopes[scope.$id] && scopes[scope.$id].movePath) {
var temp = $templateCache.get(scopes[scope.$id].movePath);
if (temp) {
return temp;
}
}
return $templateCache.get(movePath);
}
};
return InitTreeDnDTemplate;
}]
);
angular.module('ntt.TreeDnD')
.factory('$TreeDnDViewport', fnInitTreeDnDViewport);
fnInitTreeDnDViewport.$inject = ['$window', '$document', '$timeout', '$q', '$compile'];
function fnInitTreeDnDViewport($window, $document, $timeout, $q, $compile) {
var viewport,
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);
eWindow.on('load resize scroll', updateDelayed);
return $initViewport;
function update() {
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
};
if (isUpdating || isRender) {
updateAgain = true;
return; // jmp out
}
isUpdating = true;
recursivePromise();
}
function recursivePromise() {
if (isRender) {
return;
}
var number = number > 0 ? number : items.length, item;
if (number > 0) {
item = items[0];
isRender = true;
renderTime = $timeout(function () {
//item.element.html(nodeTemplate);
//$compile(item.element.contents())(item.scope);
items.splice(0, 1);
isRender = false;
number--;
$timeout.cancel(renderTime);
recursivePromise();
}, 0);
} else {
isUpdating = false;
if (updateAgain) {
updateAgain = false;
update();
}
}
}
/**
* 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;
}
/**
* Set the viewport element
*
* @name setViewport
* @param element
* @callback setViewport
* @private
*/
function setViewport(element) {
viewport = element;
}
/**
* Return the current viewport
*
* @returns {*}
* @callback getViewport
* @private
*/
function getViewport() {
return viewport;
}
/**
* trigger an update
*/
function updateDelayed() {
$timeout.cancel(updateTimeout);
updateTimeout = $timeout(
function () {
update();
},
0
);
}
/**
* Add listener for event
* @param {$scope} scope
* @param {DOMElement} element
*/
function add(scope, element) {
updateDelayed();
items.push({
element: element,
scope: scope
});
}
/**
*
*
* @param {$scope} scope
* @param {string} template
* @callback setTemplate
* @private
*/
function setTemplate(scope, template) {
nodeTemplate = template;
}
/**
* Get list of items
* @returns {Node[]}
*/
function getItems() {
return items;
}
}
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
) {
return true;
}
}
return false;
}
function _fnDragStart(e, $params) {
if (!$params.hasTouch && (e.button === 2 || e.which === 3)) {
// disable right click
return; // jmp out
}
if (e.uiTreeDragging || e.originalEvent && e.originalEvent.uiTreeDragging) { // event has already fired in other scope.
return; // jmp out
}
// the element which is clicked.
var eventElm = angular.element(e.target),
eventScope;
if ($TreeDnDHelper.isTreeDndNodeHandle(eventElm)) {
eventScope = eventElm.controller('treeDndNodeHandle').scope;
} else {
eventElm = $TreeDnDHelper.closestByAttr(eventElm, $TreeDnDHelper.isTreeDndNodeHandle);
if (eventElm) {
eventScope = eventElm.controller('treeDndNodeHandle').scope;
}
}
if (!eventScope || !eventScope.$type) {
return; // jmp out
}
// if (eventScope.$type !== 'TreeDnDNode') { // Check if it is a node or a handle
// return; // jmp out
// }
if (eventScope.$type !== 'TreeDnDNodeHandle') { // If the node has a handle, then it should be clicked by the handle
return; // jmp out
}
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; // jmp out
}
// 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; // jmp out
}
eventElm = eventElm.parent();
}
e.uiTreeDragging = true; // stop event bubbling
if (e.originalEvent) {
e.originalEvent.uiTreeDragging = true;
}
e.preventDefault();
dragScope = eventScope.getScopeNode();
$params.dragInfo = $TreeDnDHelper.dragInfo(dragScope);
if (!_$scope.$callbacks.beforeDrag(dragScope, $params.dragInfo)) {
return; // jmp out
}
$params.firstMoving = true;
_$scope.setDragging($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
}
);
$params.offsetEdge = 0;
var _width = $TreeDnDHelper.width(dragScope.$element),
_scope = dragScope,
_element = _scope.$element,
_clone,
_needCollapse = !!_$scope.enabledCollapse,
_copied = false,
_tbody,
_frag;
if (_scope.isTable) {
$params.offsetEdge = $params.dragInfo.node.__level__ - 1;
_tbody = angular.element(document.createElement('tbody'));
_frag = angular.element(document.createDocumentFragment());
_$scope.for_all_descendants(
$params.dragInfo.node, function (_node, _parent) {
_scope = _$scope.getScope(_node);
_element = _scope && _scope.$element;
if (_scope && _element) {
if (!_copied) {
_clone = _element.clone();
$TreeDnDHelper.replaceIndent(
_$scope,
_clone,
_node.__level__ - $params.offsetEdge,
'padding-left'
);
_frag.append(_clone);
// skip all, just clone parent
if (_needCollapse) {
_copied = true;
}
// 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;
},
undefined,
!_needCollapse
);
_tbody.append(_frag);
$params.dragElm.append(_tbody);
} else {
_clone = _element.clone();
if (_needCollapse) {
_clone[0].querySelector('[tree-dnd-nodes]').remove();
}
// 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'
}
);
// 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.placeElm.css('width', _width);
}
_$scope.showPlace();
_$scope.targeting = true;
if (_$scope.enabledStatus) {
_$scope.refreshStatus();
_$scope.setPositionStatus(e);
}
angular.element($params.$document).bind('touchend', $params.dragEndEvent);
angular.element($params.$document).bind('touchcancel', $params.dragEndEvent);
angular.element($params.$document).bind('touchmove', $params.dragMoveEvent);
angular.element($params.$document).bind('mouseup', $params.dragEndEvent);
angular.element($params.$document).bind('mousemove', $params.dragMoveEvent);
angular.element($params.$document).bind('mouseleave', $params.dragCancelEvent);
$params.document_height = Math.max(
$params.body.scrollHeight,
$params.body.offsetHeight,
$params.html.clientHeight,
$params.html.scrollHeight,
$params.html.offsetHeight
);
$params.document_width = Math.max(
$params.body.scrollWidth,
$params.body.offsetWidth,
$params.html.clientWidth,
$params.html.scrollWidth,
$params.html.offsetWidth
);
}
function _fnDragMove(e, $params) {
var _$scope = $params.$scope;
if (!$params.dragStarted) {
if (!$params.dragDelaying) {
$params.dragStarted = true;
_$scope.$safeApply(function () {
_$scope.$callbacks.dragStart($params.dragInfo);
});
}
return; // jmp out
}
if ($params.dragElm) {
e.preventDefault();
if ($params.$window.getSelection) {
$params.$window.getSelection().removeAllRanges();
} else if ($params.$window.document.selection) {
$params.$window.document.selection.empty();
}
var eventObj = $TreeDnDHelper.eventObj(e),
leftElmPos = eventObj.pageX - $params.pos.offsetX,
topElmPos = eventObj.pageY - $params.pos.offsetY;
//dragElm can't leave the screen on the left
if (leftElmPos < 0) {
leftElmPos = 0;
}
//dragElm can't leave the screen on the top
if (topElmPos < 0) {
topElmPos = 0;
}
//dragElm can't leave the screen on the bottom
if (topElmPos + 10 > $params.document_height) {
topElmPos = $params.document_height - 10;
}
//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'
}
);
if (_$scope.enabledStatus) {
_$scope.setPositionStatus(e);
}
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);
}
$TreeDnDHelper.positionMoved(e, $params.pos, $params.firstMoving);
if ($params.firstMoving) {
$params.firstMoving = false;
return; // jmp out
}
// check if add it as a child node first
var targetX = eventObj.pageX - $params.$window.document.body.scrollLeft,
targetY = eventObj.pageY - (window.pageYOffset || $params.$window.document.documentElement.scrollTop),
targetElm,
targetScope,
targetBefore,
targetOffset,
isChanged = true,
isVeritcal = true,
isEmpty,
isSwapped,
_scope,
_target,
_parent,
_info = $params.dragInfo,
_move = _info.move,
_drag = _info.node,
_drop = _info.drop,
treeScope = _info.target,
fnSwapTree,
isHolder = _fnPlaceHolder(e, $params);
if (!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);
targetElm = angular.element($params.$window.document.elementFromPoint(targetX, targetY));
if (!$TreeDnDHelper.isTreeDndDroppable(targetElm)) {
targetElm = $TreeDnDHelper.closestByAttr(targetElm, $TreeDnDHelper.isTreeDndDroppable);
}
if ($TreeDnDHelper.isTreeDndNode(targetElm)) {
targetScope = targetElm.controller('treeDndNode').scope;
} else if ($TreeDnDHelper.isTreeDndNodes(targetElm)) {
targetScope = targetElm.controller('treeDndNodes').scope;
} else if ($TreeDnDHelper.isTreeDndNodeHandle(targetElm)) {
targetScope = targetElm.controller('treeDndNodeHandle').scope;
}
if (!targetScope || !targetScope.$callbacks || !targetScope.$callbacks.droppable()) {
// Not allowed Drop Item
return; // jmp out
}
fnSwapTree = function () {
treeScope = targetScope.getScopeTree();
_target = _info.target;
if (_info.target !== treeScope) {
// Replace by place-holder new
_target.hidePlace();
_target.targeting = false;
treeScope.targeting = true;
_info.target = treeScope;
$params.placeElm = treeScope.initPlace(targetScope.$element, $params.dragElm);
_target = undefined;
isSwapped = true;
}
return true;
};
if (angular.isFunction(targetScope.getScopeNode)) {
targetScope = targetScope.getScopeNode();
if (!fnSwapTree()) {
return; // jmp out
}
} else {
if (targetScope.$type === 'TreeDnDNodes' || targetScope.$type === 'TreeDnD') {
if (targetScope.tree_nodes) {
if (targetScope.tree_nodes.length === 0) {
if (!fnSwapTree()) {
return; // jmp out
}
// Empty
isEmpty = true;
}
} else {
return; // jmp out
}
} else {
return; // jmp out
}
}
}
if ($params.pos.dirAx && !isSwapped || isHolder) {
isVeritcal = false;
targetScope = _info.scope;
}
if (!targetScope.$element && !targetScope) {
return; // jmp out
}
if (isEmpty) {
_move.parent = undefined;
_move.pos = 0;
_drop = undefined;
} else {
// move vertical
if (isVeritcal) {
targetElm = targetScope.$element; // Get the element of tree-dnd-node
if (angular.isUndefinedOrNull(targetElm)) {
return; // jmp out
}
targetOffset = $TreeDnDHelper.offset(targetElm);
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);
if (targetScope.getElementChilds()) {
_height -= -$TreeDnDHelper.height(targetScope.getElementChilds());
}
if (eventObj.pageY > targetOffset.top + _height) {
return; // jmp out
}
targetBefore = eventObj.pageY < targetOffset.top + _height / 2;
}
}
if (!angular.isFunction(targetScope.getData)) {
return; // jmp out
}
_target = targetScope.getData();
_parent = targetScope.getNode(_target.__parent_real__);
if (targetBefore) {
var _prev = targetScope.getPrevSibling(_target);
_move.parent = _parent;
_move.pos = angular.isDefined(_prev) ? _prev.__index__ + 1 : 0;
_drop = _prev;
} else {
if (_target.__expanded__ && !(_target.__children__.length === 1 && _target.__index_real__ === _drag.__parent_real__)) {
_move.parent = _target;
_move.pos = 0;
_drop = undefined;
} else {
_move.parent = _parent;
_move.pos = _target.__index__ + 1;
_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; // jmp out
}
}
if (_info.drag === _info.target && _parent === _drag && _$scope.enabledMove) {
_parent = treeScope.getPrevSibling(_parent);
}
if (_parent && _parent.__visible__) {
var _len = _parent.__children__.length;
_move.parent = _parent;
_move.pos = _len;
if (_len > 0) {
_drop = _parent.__children__[_len - 1];
} else {
_drop = undefined;
}
} else {
// Not changed
return; // jmp out
}
} 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__);
_move.parent = _parent;
_move.pos = _target.__index__ + 1;
_drop = _target;
} else {
// Not changed
return; // jmp out
}
} else {
return; // jmp out
}
} else {
// limited
return;
}
}
}
if (_info.drag === _info.target &&
_move.parent &&
_drag.__parent_real__ === _move.parent.__index_real__ &&
_drag.__index__ === _move.pos
) {
isChanged = false;
}
if (treeScope.$callbacks.accept(_info, _move, isChanged)) {
_info.move = _move;
_info.drop = _drop;
_info.changed = isChanged;
_info.scope = targetScope;
if (targetScope.isTable) {
$TreeDnDHelper.replaceIndent(
treeScope,
$params.placeElm,
angular.isUndefinedOrNull(_move.parent) ? 1 : _move.parent.__level__ + 1
);
if (_drop) {
_parent = (_move.parent ? _move.parent.__children__ : undefined) || _info.target.treeData;
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);
} 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);
});
}
}
}
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);
});
// 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;
},
undefined,
true
);
} else {
if (_$scope.$class.hidden) {
_element.removeClass(_$scope.$class.hidden);
}
}
$params.dragElm.remove();
$params.dragElm = undefined;
if (_$scope.enabledStatus) {
_$scope.hideStatus();
}
if (_$scope.$$apply) {
_$scope.$safeApply(function () {
var _status = _$scope.$callbacks.dropped(
$params.dragInfo,
_passed
);
_$scope.$callbacks.dragStop($params.dragInfo, _status);
clearData();
});
} else {
_fnBindDrag($params);
_$scope.$safeApply(function () {
_$scope.$callbacks.dragStop($params.dragInfo, false);
clearData();
});
}
}
function clearData() {
$params.dragInfo.target.hidePlace();
$params.dragInfo.target.targeting = false;
$params.dragInfo = undefined;
_$scope.$$apply = false;
_$scope.setDragging(undefined);
}
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);
}
}
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);
});
}
function _fnKeydownHandler(e, $params) {
var _$scope = $params.$scope;
if (e.keyCode === 27) {
if (_$scope.enabledStatus) {
_$scope.hideStatus();
}
_$scope.$$apply = false;
_fnDragEnd(e, $params);
} else {
if (_$scope.enabledHotkey && e.shiftKey) {
_$scope.enableMove(true);
if (_$scope.enabledStatus) {
_$scope.refreshStatus();
}
if (!$params.dragInfo) {
return; // jmp out
}
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.addClass(_$scope.$class.hidden);
}
}
return _node.__visible__ === false || _node.__expanded__ === false;
},
undefined,
true
);
} else {
if (_$scope.$class.hidden) {
_element.addClass(_$scope.$class.hidden);
}
}
}
}
}
function _fnKeyupHandler(e, $params) {
var _$scope = $params.$scope;
if (_$scope.enabledHotkey && !e.shiftKey) {
_$scope.enableMove(false);
if (_$scope.enabledStatus) {
_$scope.refreshStatus();
}
if (!$params.dragInfo) {
return; // jmp out
}
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;
},
undefined,
true
);
} else {
if (_$scope.$class.hidden) {
_element.removeClass(_$scope.$class.hidden);
}
}
}
}
function _$init(scope, element, $window, $document) {
var $params = {
hasTouch: 'ontouchstart' in window,
firstMoving: undefined,
dragInfo: undefined,
pos: undefined,
placeElm: undefined,
dragElm: undefined,
dragDelaying: true,
dragStarted: false,
dragTimer: undefined,
body: document.body,
html: document.documentElement,
document_height: undefined,
document_width: undefined,
offsetEdge: undefined,
$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);
};
scope.dragEnd = function (e) {
$params.dragEnd(e);
};
$params.bindDrag();
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();
}
if (scope.placeElm) {
scope.placeElm.remove();
}
});
}
return _$init;
}
]);
angular.module('ntt.TreeDnD')
.factory('$TreeDnDControl', function () {
function fnSetCollapse(node) {
node.__expanded__ = false;
}
/**
* Function set expand
* @callback fnSetExpand
* @param {Node} node
*/
function fnSetExpand(node) {
node.__expanded__ = true;
}
function _$init(scope) {
/**
* Object Tree with field function custom
*
* @namespace
* @alias $scope.tree
*/
var _tree = {
selected_node: undefined,
on_select: undefined,
/**
* @type {$scope.for_all_descendants}
*/
for_all_descendants: scope.for_all_descendants,
/**
* Select node in tree
*
* @param {Node|undefined} node
*
* @returns {Node|undefined}
*/
select_node: function (node) {
var tree = scope.tree;
var _selected = tree.deselect_node();
if (typeof node === 'object' && node !== _selected) {
node.__selected__ = true;
tree.selected_node = node;
tree.expand_all_parents(node);
if (typeof tree.on_select === 'function') {
tree.on_select(node);
}
}
return node;
},
/**
* Deselect node
*
* @returns {Node|undefined}
*/
deselect_node: function () {
var tree = scope.tree;
var _target;
if (typeof tree.selected_node === 'object') {
tree.selected_node.__selected__ = undefined;
delete tree.selected_node.__selected__;
_target = tree.selected_node;
tree.selected_node = undefined;
}
return _target;
},
/**
* Get parent of node selecting
*
* @param {Node|undefined} node
*
* @returns {Node|undefined}
*/
get_parent: function (node) {
var tree = scope.tree;
node = node || tree.selected_node;
if (node && node.__parent_real__ !== undefined) {
return scope.tree_nodes[node.__parent_real__];
}
},
/**
* Foreach ancestors in node
*
* @param {Node|undefined} node
* @param {fnSetExpand} fn - Function callback
*
* @returns {boolean}
*/
for_all_ancestors: function (node, fn) {
var tree = scope.tree;
var _parent = tree.get_parent(node);
if (_parent) {
if (fn(_parent)) {
return false;
}
return tree.for_all_ancestors(_parent, fn);
}
return true;
},
/**
* Expand all parents of node selecting
*
* @param {Node|undefined} node
*/
expand_all_parents: function (node) {
var tree = scope.tree;
node = node || tree.selected_node;
if (typeof node === 'object') {
tree.for_all_ancestors(node, fnSetExpand);
}
},
/**
* Collapse all parents of node selecting
*
* @param {Node|undefined} node
*/
collapse_all_parents: function (node) {
var tree = scope.tree;
node = node || tree.selected_node;
if (typeof node === 'object') {
tree.for_all_ancestors(node, fnSetCollapse);
}
},
/**
* Reload data in scope
*
* @returns {Node|Node[]|undefined}
*/
reload_data: function () {
return scope.reload_data();
},
/**
* Add node into parent
*
* @param {Node|Node[]|undefined} parent
* @param {Node} new_node
* @param {undefined|int} [index]
* @param {boolean} [parent_auto_expand=false]
*
* @returns {Object}
*/
add_node: function (parent, new_node, index, parent_auto_expand) {
if (typeof parent === 'object') {
if (typeof parent.__children__ !== 'object') {
parent.__children__ = [];
}
if (index >= 0) {
parent.__children__.splice(index, 0, new_node);
} else {
parent.__children__.push(new_node);
}
if (parent_auto_expand) {
parent.__expanded__ = true;
}
} else {
if (index >= 0) {
scope.treeData.splice(index, 0, new_node);
} else {
scope.treeData.push(new_node);
}
}
return new_node;
},
/**
* Add node into root
*
* @param {Node|undefined} new_node
*
* @returns {Node|undefined}
*/
add_node_root: function (new_node) {
if (typeof new_node === 'object') {
var tree = scope.tree;
tree.add_node(undefined, new_node);
}
return new_node;
},
/**
* Expand all node
*/
expand_all: function () {
var tree = scope.tree;
var len = scope.treeData.length;
for (var i = 0; i < len; i++) {
tree.for_all_descendants(scope.treeData[i], fnSetExpand);
}
},
/**
* Collapse all node
*/
collapse_all: function () {
var tree = scope.tree;
var len = scope.treeData.length;
for (var i = 0; i < len; i++) {
tree.for_all_descendants(scope.treeData[i], fnSetCollapse);
}
},
/**
* Remove node (or node selecting)
*
* @param {Node|undefined} node - If `node` is Object then delete `node` else delete `node` selecting
*/
remove_node: function (node) {
var tree = scope.tree;
node = node || tree.selected_node;
if (typeof node === 'object') {
var _parent;
if (node.__parent_real__ !== undefined) {
_parent = tree.get_parent(node).__children__;
} else {
_parent = scope.treeData;
}
_parent.splice(node.__index__, 1);
tree.reload_data();
if (tree.selected_node === node) {
tree.selected_node = undefined;
}
}
},
/**
* Expand node (or node selecting)
*
* @param {Node|undefined} node
*
* @returns {Node|undefined}
*/
expand_node: function (node) {
var tree = scope.tree;
node = node || tree.selected_node;
if (typeof node === 'object') {
node.__expanded__ = true;
return node;
}
},
/**
* Collapse node (or node selecting)
*
* @param {Node|undefined} node
*
* @returns {Node|undefined}
*/
collapse_node: function (node) {
var tree = scope.tree;
node = node || tree.selected_node;
if (typeof node === 'object') {
node.__expanded__ = false;
return node;
}
},
/**
* Get node selected
*
* @returns {Node|undefined}
*/
get_selected_node: function () {
var tree = scope.tree;
return tree.selected_node;
},
/**
* Get node first in root (or selecting)
*
* @returns {Node|undefined}
*/
get_first_node: function () {
var tree = scope.tree;
var wrapper = tree.selected_node;
if (wrapper === undefined) {
wrapper = scope.treeData;
}
if (typeof wrapper === 'object') {
var len = wrapper.length;
if (len > 0) {
return wrapper[0];
}
}
},
/**
* Get children of node (or selecting)
*
* @param {Node|undefined} node
*
* @returns {undefined|Node[]}
*/
get_children: function (node) {
var tree = scope.tree;
if (node === undefined && tree.selected_node === undefined) {
return tree.treeData;
}
node = node || tree.selected_node;
if (typeof node === 'object' && node.__children__ !== undefined) {
return node.__children__;
}
},
/**
* Get siblings
*
* @param {Node|undefined} node
*
* @returns {undefined|Node[]}
*/
get_siblings: function (node) {
var tree = scope.tree;
node = node || tree.selected_node;
if (typeof node === 'object') {
var _parent = tree.get_parent(node),
_target;
if (_parent) {
_target = _parent.__children__;
} else {
_target = scope.treeData;
}
return _target;
}
},
/**
* Get next sibling
*
* @param {Node|undefined} node
*
* @returns {Node|undefined}
*/
get_next_sibling: function (node) {
var tree = scope.tree;
node = node || tree.selected_node;
if (typeof node === 'object') {
var _target = tree.get_siblings(node);
var n = _target.length;
if (node.__index__ < n) {
return _target[node.__index__ + 1];
}
}
},
/**
* Get previous sibling
*
* @param {Node|undefined} node
*
* @returns {Node|undefined}
*/
get_prev_sibling: function (node) {
var tree = scope.tree;
node = node || tree.selected_node;
var _target = tree.get_siblings(node);
if (node.__index__ > 0) {
return _target[node.__index__ - 1];
}
},
/**
* Get first child
*
* @param {Node|undefined} node
*
* @returns {Node|undefined}
*/
get_first_child: function (node) {
var tree = scope.tree;
node = node || tree.selected_node;
if (typeof node === 'object') {
var _target = node.__children__;
if (_target && _target.length > 0) {
return node.__children__[0];
}
}
},
/**
* Get closest ancestor next sibling
*
* @param {Node|undefined} node
*
* @returns {Node|undefined}
*/
get_closest_ancestor_next_sibling: function (node) {
var tree = scope.tree;
node = node || tree.selected_node;
var _target = tree.get_next_sibling(node);
if (_target) {
return _target;
}
var _parent = tree.get_parent(node);
if (_parent) {
return tree.get_closest_ancestor_next_sibling(_parent);
}
},
/**
* Get next node
*
* @param {Node|undefined} node
*
* @returns {Node|undefined}
*/
get_next_node: function (node) {
var tree = scope.tree;
node = node || tree.selected_node;
if (typeof node === 'object') {
var _target = tree.get_first_child(node);
if (_target) {
return _target;
} else {
return tree.get_closest_ancestor_next_sibling(node);
}
}
},
/**
* Get previous node
*
* @param {Node|undefined} node
*
* @returns {Node|undefined}
*/
get_prev_node: function (node) {
var tree = scope.tree;
node = node || tree.selected_node;
if (typeof node === 'object') {
var _target = tree.get_prev_sibling(node);
if (_target) {
return tree.get_last_descendant(_target);
}
return tree.get_parent(node);
}
},
get_last_descendant: scope.getLastDescendant,
/**
* Select parent node
*
* @param {Node|undefined} node
*
* @returns {Node|undefined}
*/
select_parent_node: function (node) {
var tree = scope.tree;
node = node || tree.selected_node;
if (typeof node === 'object') {
var _parent = tree.get_parent(node);
if (_parent) {
return tree.select_node(_parent);
}
}
},
/**
* Select first node
*
* @returns {Node|undefined}
*/
select_first_node: function () {
var tree = scope.tree;
var firstNode = tree.get_first_node();
return tree.select_node(firstNode);
},
/**
* Select next sibling
*
* @param {Node|undefined} node
*
* @returns {Node|undefined}
*/
select_next_sibling: function (node) {
var tree = scope.tree;
node = node || tree.selected_node;
if (typeof node === 'object') {
var _target = tree.get_next_sibling(node);
if (_target) {
return tree.select_node(_target);
}
}
},
/**
* Select previous sibling
*
* @param {Node|undefined} node
*
* @returns {*|Object}
*/
select_prev_sibling: function (node) {
var tree = scope.tree;
node = node || tree.selected_node;
if (typeof node === 'object') {
var _target = tree.get_prev_sibling(node);
if (_target) {
return tree.select_node(_target);
}
}
},
/**
* Select next node
*
* @param {Node|undefined} node
*
* @returns {Node|undefined}
*/
select_next_node: function (node) {
var tree = scope.tree;
node = node || tree.selected_node;
if (typeof node === 'object') {
var _target = tree.get_next_node(node);
if (_target) {
return tree.select_node(_target);
}
}
},
/**
* Select previous node
*
* @param {Node|undefined} node
*
* @returns {Node|undefined}
*/
select_prev_node: function (node) {
var tree = scope.tree;
node = node || tree.selected_node;
if (typeof node === 'object') {
var _target = tree.get_prev_node(node);
if (_target) {
return tree.select_node(_target);
}
}
}
};
return _tree;
}
return _$init;
});
angular.module('template/TreeDnD/TreeDnD.html', []).run(
['$templateCache', function ($templateCache) {
$templateCache.put(
'template/TreeDnD/TreeDnD.html',
'<table ng-class="$tree_class">' +
' <thead>' +
' <tr>' +
' <th ng-class="expandingProperty.titleClass" ng-style="expandingProperty.titleStyle">' +
' {{expandingProperty.displayName || expandingProperty.field || expandingProperty}}' +
' <\/th>' +
' <th ng-repeat="col in colDefinitions" ng-class="col.titleClass" ng-style="col.titleStyle">' +
' {{col.displayName || col.field}}' +
' </th>' +
' </tr>' +
' </thead>' +
' <tbody tree-dnd-nodes>' +
' <tr tree-dnd-node="node" ng-repeat="node in tree_nodes track by node.__hashKey__" ' +
' ng-if="(node.__inited__ || node.__visible__)"' +
' ng-click="onSelect(node)" ' +
' ng-class="(node.__selected__ ? \' active\':\'\')">' +
' <td tree-dnd-node-handle' +
' ng-style="expandingProperty.cellStyle ? expandingProperty.cellStyle : {\'padding-left\': $callbacks.calsIndent(node.__level__)}"' +
' ng-class="expandingProperty.cellClass"' +
' compile="expandingProperty.cellTemplate">' +
' <a data-nodrag>' +
' <i ng-class="node.__icon_class__" ng-click="toggleExpand(node)" class="tree-icon"></i>' +
' </a>' +
' {{node[expandingProperty.field] || node[expandingProperty]}}' +
' </td>' +
' <td ng-repeat="col in colDefinitions" ng-class="col.cellClass" ng-style="col.cellStyle" compile="col.cellTemplate">' +
' {{node[col.field]}}' +
' </td>' +
' </tr>' +
' </tbody>' +
'</table>'
);
$templateCache.put(
'template/TreeDnD/TreeDnDStatusCopy.html',
'<label><i class="fa fa-copy"></i> <b>Copying</b></label>'
);
$templateCache.put(
'template/TreeDnD/TreeDnDStatusMove.html',
'<label><i class="fa fa-file-text"></i> <b>Moving</b></label>'
);
}]
);
})();