You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

6148 lines
296 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

layui.define(['jquery'],function(exports){
// 获取layui的jQuery模块
}
let jQuery = layui.jquery;
(function ($) {
// 定义设置对象、根节点对象和缓存对象
var settings = {}, roots = {}, caches = {},
// 核心默认常量
_consts = {
className: {
BUTTON: "button", // 按钮类名
LEVEL: "level", // 层级类名
ICO_LOADING: "ico_loading", // 加载图标类名
SWITCH: "switch", // 开关类名
NAME: 'node_name' // 节点名称类名
},
event: {
// 节点创建事件
NODECREATED: "ztree_nodeCreated",
// 点击事件
CLICK: "ztree_click",
// 展开事件
EXPAND: "ztree_expand",
// 折叠事件
COLLAPSE: "ztree_collapse",
// 异步加载成功事件
ASYNC_SUCCESS: "ztree_async_success",
// 异步加载错误事件
ASYNC_ERROR: "ztree_async_error",
// 移除事件
REMOVE: "ztree_remove",
// 选中事件
SELECTED: "ztree_selected",
// 取消选中事件
UNSELECTED: "ztree_unselected"
},
id: {
// 定义一个对象用于存储不同元素的ID前缀
A: "_a", // 为元素A设置ID前缀为"_a"
ICON: "_ico", // 为ICON元素设置ID前缀为"_ico"
SPAN: "_span", // 为SPAN元素设置ID前缀为"_span"
SWITCH: "_switch", // 为SWITCH元素设置ID前缀为"_switch"
UL: "_ul" // 为UL元素设置ID前缀为"_ul"
},
line: {
// 定义一个对象,用于存储不同线条类型的名称
ROOT: "root", // 根线条类型
ROOTS: "roots", // 多根线条类型
CENTER: "center", // 中心线条类型
BOTTOM: "bottom", // 底部线条类型
NOLINE: "noline", // 无线条类型
LINE: "line" // 普通线条类型
},
folder: {
// 定义文件夹状态为打开
OPEN: "open",
// 定义文件夹状态为关闭
CLOSE: "close",
// 定义文件夹类型为文档
DOCU: "docu"
},
node: {
// 当前选中的节点
CURSELECTED: "curSelectedNode"
}
},
//default setting of core
_setting = {
// 树的ID用于标识树的唯一性
treeId: "",
// 树的对象实例初始化为null
treeObj: null,
// 视图设置对象
view: {
// 自定义DOM节点添加函数默认为null
addDiyDom: null,
// 是否自动取消选中状态默认为true
autoCancelSelected: true,
// 双击展开节点默认为true
dblClickExpand: true,
// 展开速度,可选值为"fast"或"slow"
expandSpeed: "fast",
// 字体样式设置,默认为空对象
fontCss: {},
// 节点类名设置,默认为空对象
nodeClasses: {},
// 节点名称是否作为HTML解析默认为false
nameIsHTML: false,
// 是否允许多选默认为true
selectedMulti: true,
// 是否显示图标默认为true
showIcon: true,
// 是否显示连接线默认为true
showLine: true,
// 是否显示标题默认为true
showTitle: true,
// 文本是否可选择默认为false
txtSelectedEnable: false
},
data: {
// 定义树形结构数据的键名
key: {
// 表示节点是否有子节点的键名
isParent: "isParent",
// 表示子节点集合的键名
children: "children",
// 表示节点名称的键名
name: "name",
// 表示节点标题的键名,初始为空字符串
title: "",
// 表示节点URL的键名
url: "url",
// 表示节点图标的键名
icon: "icon"
},
// 定义渲染时使用的键名
render: {
// 渲染时使用的名称键名初始为null
name: null,
// 渲染时使用的标题键名初始为null
title: null,
},
simpleData: {
// 启用或禁用简单数据模式
enable: false,
// 节点的唯一标识键名
idKey: "id",
// 父节点的标识键名
pIdKey: "pId",
// 根节点的父节点标识默认为null表示没有父节点
rootPId: null
},
keep: {
// 是否保留父节点信息
parent: false,
// 是否保留叶子节点信息
leaf: false
}
},
async: {
// 是否启用异步请求默认为false
enable: false,
// 设置请求的内容类型,默认为"application/x-www-form-urlencoded"
contentType: "application/x-www-form-urlencoded",
// 设置请求的类型,默认为"post"
type: "post",
// 设置响应的数据类型,默认为"text"
dataType: "text",
// 设置请求的头部信息,默认为空对象
headers: {},
// 设置XHR对象的额外字段默认为空对象
xhrFields: {},
// 设置请求的URL地址默认为空字符串
url: "",
// 自动添加到请求参数中的键值对数组,默认为空数组
autoParam: [],
// 其他手动添加的请求参数键值对数组,默认为空数组
otherParam: [],
// 数据过滤器函数用于处理响应数据默认为null
dataFilter: null
},
callback: {
// 在异步操作之前执行的回调函数默认为null
beforeAsync: null,
// 在节点被点击之前执行的回调函数默认为null
beforeClick: null,
// 在节点被双击之前执行的回调函数默认为null
beforeDblClick: null,
// 在节点被右键点击之前执行的回调函数默认为null
beforeRightClick: null,
// 在鼠标按下节点之前执行的回调函数默认为null
beforeMouseDown: null,
// 在鼠标释放节点之前执行的回调函数默认为null
beforeMouseUp: null,
// 在节点展开之前执行的回调函数默认为null
beforeExpand: null,
// 在节点折叠之前执行的回调函数默认为null
beforeCollapse: null,
// 在节点移除之前执行的回调函数默认为null
beforeRemove: null,
// 异步操作失败时执行的回调函数默认为null
onAsyncError: null,
// 异步操作成功时执行的回调函数默认为null
onAsyncSuccess: null,
// 节点创建后执行的回调函数默认为null
onNodeCreated: null,
// 节点被点击时执行的回调函数默认为null
onClick: null,
// 节点被双击时执行的回调函数默认为null
onDblClick: null,
// 节点被右键点击时执行的回调函数默认为null
onRightClick: null,
// 鼠标按下节点时执行的回调函数默认为null
onMouseDown: null,
// 鼠标释放节点时执行的回调函数默认为null
onMouseUp: null,
// 节点展开时执行的回调函数默认为null
onExpand: null,
// 节点折叠时执行的回调函数默认为null
onCollapse: null,
// 节点移除时执行的回调函数默认为null
onRemove: null
}
},
//default root of core
//zTree use root to save full data
/**
* 初始化根节点
* @param {Object} setting - 配置对象
*/
_initRoot = function (setting) {
// 获取根节点,如果不存在则创建一个新的空对象
var r = data.getRoot(setting);
if (!r) {
r = {};
// 将新创建的根节点设置到数据中
data.setRoot(setting, r);
}
// 初始化根节点的子节点为空数组
data.nodeChildren(setting, r, []);
// 初始化根节点的扩展触发标志为false
r.expandTriggerFlag = false;
// 初始化当前选中列表为空数组
r.curSelectedList = [];
// 初始化无选择状态为true
r.noSelection = true;
// 初始化已创建节点列表为空数组
r.createdNodes = [];
// 初始化zId为0
r.zId = 0;
// 初始化版本号为当前时间的时间戳
r._ver = (new Date()).getTime();
},
//default cache of core
_initCache = function (setting) {
// 获取缓存对象,如果不存在则初始化一个新的空对象
var c = data.getCache(setting);
if (!c) {
// 如果缓存对象不存在,创建一个新的空对象
c = {};
// 将新的空对象设置到缓存中
data.setCache(setting, c);
}
// 初始化缓存对象的nodes属性为一个空数组
c.nodes = [];
// 初始化缓存对象的doms属性为一个空数组
c.doms = [];
},
//default bindEvent of core
_bindEvent = function (setting) {
// 获取树对象和事件常量
var o = setting.treeObj,
c = consts.event;
// 绑定节点创建事件
o.bind(c.NODECREATED, function (event, treeId, node) {
tools.apply(setting.callback.onNodeCreated, [event, treeId, node]);
});
// 绑定点击事件
o.bind(c.CLICK, function (event, srcEvent, treeId, node, clickFlag) {
tools.apply(setting.callback.onClick, [srcEvent, treeId, node, clickFlag]);
});
// 绑定展开事件
o.bind(c.EXPAND, function (event, treeId, node) {
tools.apply(setting.callback.onExpand, [event, treeId, node]);
});
// 绑定折叠事件
o.bind(c.COLLAPSE, function (event, treeId, node) {
tools.apply(setting.callback.onCollapse, [event, treeId, node]);
});
// 绑定异步加载成功事件
o.bind(c.ASYNC_SUCCESS, function (event, treeId, node, msg) {
tools.apply(setting.callback.onAsyncSuccess, [event, treeId, node, msg]);
});
// 绑定异步加载错误事件
o.bind(c.ASYNC_ERROR, function (event, treeId, node, XMLHttpRequest, textStatus, errorThrown) {
tools.apply(setting.callback.onAsyncError, [event, treeId, node, XMLHttpRequest, textStatus, errorThrown]);
});
// 绑定移除事件
o.bind(c.REMOVE, function (event, treeId, treeNode) {
tools.apply(setting.callback.onRemove, [event, treeId, treeNode]);
});
// 绑定选中事件
o.bind(c.SELECTED, function (event, treeId, node) {
tools.apply(setting.callback.onSelected, [treeId, node]);
});
// 绑定取消选中事件
o.bind(c.UNSELECTED, function (event, treeId, node) {
tools.apply(setting.callback.onUnSelected, [treeId, node]);
});
},
_unbindEvent = function (setting) {
// 获取树对象
var o = setting.treeObj,
// 获取事件常量
c = consts.event;
// 解绑节点创建事件
o.unbind(c.NODECREATED)
// 解绑点击事件
.unbind(c.CLICK)
// 解绑展开事件
.unbind(c.EXPAND)
// 解绑折叠事件
.unbind(c.COLLAPSE)
// 解绑异步成功事件
.unbind(c.ASYNC_SUCCESS)
// 解绑异步错误事件
.unbind(c.ASYNC_ERROR)
// 解绑移除事件
.unbind(c.REMOVE)
// 解绑选中事件
.unbind(c.SELECTED)
// 解绑取消选中事件
.unbind(c.UNSELECTED);
},
//default event proxy of core
_eventProxy = function (event) {
// 获取事件目标元素
var target = event.target,
// 获取树的设置信息
// 获取指定树的设置信息
setting = data.getSetting(event.data.treeId),
// 初始化节点ID为空字符串
tId = "",
// 初始化节点对象为null
node = null,
// 初始化节点事件类型为空字符串
nodeEventType = "",
// 初始化树事件类型为空字符串
treeEventType = "",
// 初始化节点事件回调函数为null
nodeEventCallback = null,
// 初始化树事件回调函数为null
treeEventCallback = null,
// 初始化临时变量为null
tmp = null;
// 根据事件类型判断是哪种树事件
if (tools.eqs(event.type, "mousedown")) {
// 如果事件类型是鼠标按下,则设置树事件类型为"mousedown"
treeEventType = "mousedown";
} else if (tools.eqs(event.type, "mouseup")) {
// 如果事件类型是鼠标抬起,则设置树事件类型为"mouseup"
treeEventType = "mouseup";
} else if (tools.eqs(event.type, "contextmenu")) {
// 如果事件类型是右键菜单,则设置树事件类型为"contextmenu"
treeEventType = "contextmenu";
} else if (tools.eqs(event.type, "click")) {
// 如果点击的是节点开关则设置节点事件类型为switchNode
if (tools.eqs(target.tagName, "span") && target.getAttribute("treeNode" + consts.id.SWITCH) !== null) {
// 获取主DOM元素的ID并赋值给tId
tId = tools.getNodeMainDom(target).id;
// 设置节点事件类型为"switchNode"
nodeEventType = "switchNode";
} else {
// 否则查找包含特定属性的DOM元素并设置节点事件类型为clickNode
tmp = tools.getMDom(setting, target, [{tagName: "a", attrName: "treeNode" + consts.id.A}]);
if (tmp) {
// 获取主DOM元素的ID并赋值给tId
tId = tools.getNodeMainDom(tmp).id;
// 设置节点事件类型为"clickNode"
nodeEventType = "clickNode";
}
}
} else if (tools.eqs(event.type, "dblclick")) {
// 双击事件处理设置树事件类型为dblclick
treeEventType = "dblclick";
// 获取目标元素中包含特定属性的子元素
tmp = tools.getMDom(setting, target, [{tagName: "a", attrName: "treeNode" + consts.id.A}]);
if (tmp) {
// 获取节点的主DOM元素的ID
tId = tools.getNodeMainDom(tmp).id;
// 设置节点事件类型为switchNode
nodeEventType = "switchNode";
}
}
// 检查treeEventType数组是否有元素且tId字符串是否为空
if (treeEventType.length > 0 && tId.length == 0) {
// 调用tools.getMDom方法获取目标元素的子元素该子元素具有指定的标签名和属性名
tmp = tools.getMDom(setting, target, [{tagName: "a", attrName: "treeNode" + consts.id.A}]);
// 如果找到了符合条件的元素
if (tmp) {
// 获取该元素的主DOM节点的ID并赋值给tId
tId = tools.getNodeMainDom(tmp).id;
}
}
// event to node
if (tId.length > 0) {
// 获取节点缓存
node = data.getNodeCache(setting, tId);
// 根据节点事件类型进行处理
switch (nodeEventType) {
case "switchNode" :
// 判断节点是否为父节点
var isParent = data.nodeIsParent(setting, node);
if (!isParent) {
// 如果不是父节点,则清空事件类型
nodeEventType = "";
} else if (tools.eqs(event.type, "click")
|| (tools.eqs(event.type, "dblclick") && tools.apply(setting.view.dblClickExpand, [setting.treeId, node], setting.view.dblClickExpand))) {
// 如果事件类型是点击或双击且满足展开条件,设置回调函数为切换节点处理函数
nodeEventCallback = handler.onSwitchNode;
} else {
// 否则清空事件类型
nodeEventType = "";
}
break;
case "clickNode" :
// 如果事件类型是点击节点,设置回调函数为点击节点处理函数
nodeEventCallback = handler.onClickNode;
break;
}
}
// event to zTree
switch (treeEventType) {
// 当事件类型为 "mousedown" 时,将 treeEventCallback 设置为 handler.onZTreeMousedown
case "mousedown" :
treeEventCallback = handler.onZTreeMousedown;
break;
// 当事件类型为 "mouseup" 时,将 treeEventCallback 设置为 handler.onZTreeMouseup
case "mouseup" :
treeEventCallback = handler.onZTreeMouseup;
break;
// 当事件类型为 "dblclick" 时,将 treeEventCallback 设置为 handler.onZTreeDblclick
case "dblclick" :
treeEventCallback = handler.onZTreeDblclick;
break;
// 当事件类型为 "contextmenu" 时,将 treeEventCallback 设置为 handler.onZTreeContextmenu
case "contextmenu" :
treeEventCallback = handler.onZTreeContextmenu;
break;
}
var proxyResult = {
// 停止标志初始值为false
stop: false,
// 当前节点对象
node: node,
// 节点事件类型
nodeEventType: nodeEventType,
// 节点事件回调函数
nodeEventCallback: nodeEventCallback,
// 树事件类型
treeEventType: treeEventType,
// 树事件回调函数
treeEventCallback: treeEventCallback
};
// 返回代理结果对象
return proxyResult
},
//default init node of core
_initNode = function (setting, level, n, parentNode, isFirstNode, isLastNode, openFlag) {
// 如果节点不存在,直接返回
if (!n) return;
// 获取树的根节点
var r = data.getRoot(setting),
// 获取当前节点的子节点
children = data.nodeChildren(setting, n);
// 设置当前节点的层级
n.level = level;
// 生成并设置当前节点的唯一标识符
n.tId = setting.treeId + "_" + (++r.zId);
// 设置当前节点的父节点标识符
n.parentTId = parentNode ? parentNode.tId : null;
// 根据节点的open属性设置是否展开
n.open = (typeof n.open == "string") ? tools.eqs(n.open, "true") : !!n.open;
// 判断当前节点是否是父节点
var isParent = data.nodeIsParent(setting, n);
// 如果当前节点有子节点
if (tools.isArray(children)) {
// 将当前节点标记为父节点
data.nodeIsParent(setting, n, true);
// 标记当前节点为异步加载
n.zAsync = true;
} else {
// 更新当前节点的父节点状态
isParent = data.nodeIsParent(setting, n, isParent);
// 根据父节点状态和异步加载设置决定是否展开当前节点
n.open = (isParent && !setting.async.enable) ? n.open : false;
// 标记当前节点为非异步加载
n.zAsync = !isParent;
}
n.isFirstNode = isFirstNode;
// 设置节点是否为第一个节点的函数
n.isLastNode = isLastNode;
// 设置节点是否为最后一个节点的函数
n.getParentNode = function () {
return data.getNodeCache(setting, n.parentTId);
};
// 获取父节点的方法,通过缓存获取父节点信息
n.getPreNode = function () {
return data.getPreNode(setting, n);
};
// 获取前一个兄弟节点的方法
n.getNextNode = function () {
return data.getNextNode(setting, n);
};
// 获取下一个兄弟节点的方法
n.getIndex = function () {
return data.getNodeIndex(setting, n);
};
// 获取当前节点在同级中的索引位置
n.getPath = function () {
return data.getNodePath(setting, n);
};
// 获取从根节点到当前节点的路径
n.isAjaxing = false;
// 初始化节点的Ajax请求状态为false
data.fixPIdKeyValue(setting, n);
// 修正节点的父ID键值对
},
_init = {
bind: [_bindEvent],
// 绑定事件的方法数组
unbind: [_unbindEvent],
// 解绑事件的方法数组
caches: [_initCache],
// 初始化缓存的方法数组
nodes: [_initNode],
// 初始化节点的方法数组
proxys: [_eventProxy],
// 事件代理的方法数组
roots: [_initRoot],
// 初始化根节点的方法数组
beforeA: [],
// 在操作前的回调方法数组
afterA: [],
// 在操作后的回调方法数组
innerBeforeA: [],
// 内部操作前的回调方法数组
innerAfterA: [],
// 内部操作后的回调方法数组
zTreeTools: []
// ZTree工具方法数组
},
//method of operate data
data = {
// 添加节点缓存
addNodeCache: function (setting, node) {
// 获取指定设置的缓存,并将节点添加到缓存中
data.getCache(setting).nodes[data.getNodeCacheId(node.tId)] = node;
},
// 获取节点缓存ID
getNodeCacheId: function (tId) {
// 返回节点ID的最后一部分作为缓存ID
return tId.substring(tId.lastIndexOf("_") + 1);
},
// 添加afterA事件
addAfterA: function (afterA) {
// 将afterA事件添加到初始化配置的afterA数组中
_init.afterA.push(afterA);
},
// 添加beforeA事件
addBeforeA: function (beforeA) {
// 将beforeA事件添加到初始化配置的beforeA数组中
_init.beforeA.push(beforeA);
},
// 添加innerAfterA事件
addInnerAfterA: function (innerAfterA) {
// 将innerAfterA事件添加到初始化配置的innerAfterA数组中
_init.innerAfterA.push(innerAfterA);
},
// 添加innerBeforeA事件
addInnerBeforeA: function (innerBeforeA) {
// 将innerBeforeA事件添加到初始化配置的innerBeforeA数组中
_init.innerBeforeA.push(innerBeforeA);
},
// 添加初始化绑定事件
addInitBind: function (bindEvent) {
// 将绑定事件添加到初始化配置的bind数组中
_init.bind.push(bindEvent);
},
// 添加初始化解绑事件
addInitUnBind: function (unbindEvent) {
// 将解绑事件添加到初始化配置的unbind数组中
_init.unbind.push(unbindEvent);
},
// 添加初始化缓存
addInitCache: function (initCache) {
// 将初始化缓存添加到初始化配置的caches数组中
_init.caches.push(initCache);
},
// 添加初始化节点
addInitNode: function (initNode) {
// 将初始化节点添加到初始化配置的nodes数组中
_init.nodes.push(initNode);
},
// 添加初始化代理
addInitProxy: function (initProxy, isFirst) {
if (!!isFirst) {
// 如果isFirst为真将代理插入到proxys数组的开头
_init.proxys.splice(0, 0, initProxy);
} else {
// 否则将代理添加到proxys数组的末尾
_init.proxys.push(initProxy);
}
},
addInitRoot: function (initRoot) {
// 将初始化根节点添加到_init.roots数组中
_init.roots.push(initRoot);
},
addNodesData: function (setting, parentNode, index, nodes) {
// 获取父节点的子节点数据
var children = data.nodeChildren(setting, parentNode), params;
// 如果父节点没有子节点
if (!children) {
// 为父节点创建一个新的空子节点数组
children = data.nodeChildren(setting, parentNode, []);
// 将索引设置为-1表示插入位置无效
index = -1;
} else if (index >= children.length) {
// 如果索引超出子节点数组的长度,将索引设置为-1
index = -1;
}
if (children.length > 0 && index === 0) {
// 如果子节点数组不为空且索引为0则将第一个子节点的isFirstNode属性设置为false
children[0].isFirstNode = false;
// 设置第一个子节点的图标样式
view.setNodeLineIcos(setting, children[0]);
} else if (children.length > 0 && index < 0) {
// 如果子节点数组不为空且索引小于0则将最后一个子节点的isLastNode属性设置为false
children[children.length - 1].isLastNode = false;
// 设置最后一个子节点的图标样式
view.setNodeLineIcos(setting, children[children.length - 1]);
}
// 设置父节点为父节点
data.nodeIsParent(setting, parentNode, true);
if (index < 0) {
// 如果索引小于0则将新节点添加到子节点数组的末尾
data.nodeChildren(setting, parentNode, children.concat(nodes));
} else {
// 如果索引大于等于0则在指定位置插入新节点
params = [index, 0].concat(nodes);
children.splice.apply(children, params);
}
},
addSelectedNode: function (setting, node) {
// 获取根节点
var root = data.getRoot(setting);
// 如果节点未被选中,则将其添加到当前选中列表中
if (!data.isSelectedNode(setting, node)) {
root.curSelectedList.push(node);
}
},
addCreatedNode: function (setting, node) {
// 如果存在自定义的节点创建回调或添加自定义DOM的方法则执行以下逻辑
if (!!setting.callback.onNodeCreated || !!setting.view.addDiyDom) {
// 获取根节点
var root = data.getRoot(setting);
// 将新创建的节点添加到已创建节点列表中
root.createdNodes.push(node);
}
},
addZTreeTools: function (zTreeTools) {
// 将传入的zTree工具对象添加到_init.zTreeTools数组中
_init.zTreeTools.push(zTreeTools);
},
exSetting: function (s) {
// 使用jQuery的extend方法将传入的设置对象s合并到_setting对象中实现深度拷贝
$.extend(true, _setting, s);
},
fixPIdKeyValue: function (setting, node) {
// 如果启用了简单数据模式
if (setting.data.simpleData.enable) {
// 设置节点的父ID键值如果节点有父节点则获取父节点的ID否则设置为根节点的父ID
node[setting.data.simpleData.pIdKey] = node.parentTId ? node.getParentNode()[setting.data.simpleData.idKey] : setting.data.simpleData.rootPId;
}
},
getAfterA: function (setting, node, array) {
// 遍历_init.afterA数组并调用每个元素作为函数
for (var i = 0, j = _init.afterA.length; i < j; i++) {
// 使用apply方法将当前上下文和参数传递给每个函数
_init.afterA[i].apply(this, arguments);
}
},
getBeforeA: function (setting, node, array) {
// 遍历_init.beforeA数组并调用每个元素作为函数
for (var i = 0, j = _init.beforeA.length; i < j; i++) {
// 使用apply方法将当前上下文和参数传递给每个函数
_init.beforeA[i].apply(this, arguments);
}
},
getInnerAfterA: function (setting, node, array) {
// 遍历_init.innerAfterA数组并调用每个元素作为函数
for (var i = 0, j = _init.innerAfterA.length; i < j; i++) {
// 使用apply方法将当前上下文和参数传递给每个函数
_init.innerAfterA[i].apply(this, arguments);
}
},
getInnerBeforeA: function (setting, node, array) {
// 遍历_init.innerBeforeA数组并调用每个元素作为函数
for (var i = 0, j = _init.innerBeforeA.length; i < j; i++) {
// 使用apply方法将当前上下文和参数传递给每个函数
_init.innerBeforeA[i].apply(this, arguments);
}
},
getCache: function (setting) {
// 返回缓存中对应treeId的缓存数据
return caches[setting.treeId];
},
getNodeIndex: function (setting, node) {
// 如果节点为空返回null
if (!node) return null;
// 获取父节点,如果节点没有父节点则获取根节点
var p = node.parentTId ? node.getParentNode() : data.getRoot(setting),
// 获取父节点的所有子节点
children = data.nodeChildren(setting, p);
// 遍历子节点数组
for (var i = 0, l = children.length - 1; i <= l; i++) {
// 如果找到匹配的节点,返回其索引
if (children[i] === node) {
return i;
}
}
// 如果没有找到匹配的节点,返回-1
return -1;
},
getNextNode: function (setting, node) {
// 如果节点为空返回null
if (!node) return null;
// 获取父节点,如果节点没有父节点则获取根节点
var p = node.parentTId ? node.getParentNode() : data.getRoot(setting),
// 获取父节点的所有子节点
children = data.nodeChildren(setting, p);
// 遍历子节点数组
for (var i = 0, l = children.length - 1; i <= l; i++) {
// 如果找到匹配的节点
if (children[i] === node) {
// 如果是最后一个节点返回null否则返回下一个节点
return (i == l ? null : children[i + 1]);
}
}
// 如果没有找到匹配的节点返回null
return null;
},
/**
* 根据指定的参数在节点数组中查找匹配的节点。
* @param {Object} setting - 配置对象,用于获取子节点。
* @param {Array} nodes - 要搜索的节点数组。
* @param {String} key - 要匹配的键名。
* @param {*} value - 要匹配的值。
* @returns {Object|null} 返回匹配的节点如果没有找到则返回null。
*/
getNodeByParam: function (setting, nodes, key, value) {
// 如果节点数组或键名为空直接返回null
if (!nodes || !key) return null;
// 遍历节点数组
for (var i = 0, l = nodes.length; i < l; i++) {
// 获取当前节点
var node = nodes[i];
// 如果当前节点的指定键值与目标值相等,返回该节点
if (node[key] == value) {
return nodes[i];
}
// 获取当前节点的子节点
var children = data.nodeChildren(setting, node);
// 递归调用自身在子节点中查找匹配的节点
var tmp = data.getNodeByParam(setting, children, key, value);
// 如果找到匹配的节点,返回该节点
if (tmp) return tmp;
}
// 如果未找到匹配的节点返回null
return null;
},
getNodeCache: function (setting, tId) {
// 如果tId不存在返回null
if (!tId) return null;
// 从缓存中获取节点对象
var n = caches[setting.treeId].nodes[data.getNodeCacheId(tId)];
// 如果节点存在返回节点否则返回null
return n ? n : null;
},
getNodePath: function (setting, node) {
// 如果node不存在返回null
if (!node) return null;
var path;
// 如果节点有父节点,递归获取父节点的路径
if (node.parentTId) {
path = node.getParentNode().getPath();
} else {
// 如果没有父节点,初始化路径为空数组
path = [];
}
// 如果路径存在,将当前节点添加到路径中
if (path) {
path.push(node);
}
// 返回节点路径
return path;
},
getNodes: function (setting) {
// 调用data.nodeChildren方法获取根节点的子节点
return data.nodeChildren(setting, data.getRoot(setting));
},
getNodesByParam: function (setting, nodes, key, value) {
// 如果nodes或key为空返回空数组
if (!nodes || !key) return [];
var result = []; // 初始化结果数组
// 遍历所有节点
for (var i = 0, l = nodes.length; i < l; i++) {
var node = nodes[i]; // 当前节点
// 如果当前节点的指定属性值等于目标值,则将该节点加入结果数组
if (node[key] == value) {
result.push(node);
}
// 获取当前节点的子节点
var children = data.nodeChildren(setting, node);
// 递归查找子节点中符合条件的节点并合并到结果数组中
result = result.concat(data.getNodesByParam(setting, children, key, value));
}
// 返回符合条件的节点数组
return result;
},
getNodesByParamFuzzy: function (setting, nodes, key, value) {
// 如果节点或键为空,返回空数组
if (!nodes || !key) return [];
// 初始化结果数组
var result = [];
// 将搜索值转换为小写
value = value.toLowerCase();
// 遍历所有节点
for (var i = 0, l = nodes.length; i < l; i++) {
var node = nodes[i];
// 如果节点的指定键是字符串且包含搜索值,则将该节点加入结果数组
if (typeof node[key] == "string" && nodes[i][key].toLowerCase().indexOf(value) > -1) {
result.push(node);
}
// 获取当前节点的子节点
var children = data.nodeChildren(setting, node);
// 递归调用函数以查找子节点中的匹配项,并将结果合并到结果数组中
result = result.concat(data.getNodesByParamFuzzy(setting, children, key, value));
}
// 返回最终的结果数组
return result;
},
/**
* 根据过滤条件获取节点
* @param {Object} setting - 配置对象
* @param {Array} nodes - 节点数组
* @param {Function} filter - 过滤函数
* @param {Boolean} isSingle - 是否只返回一个节点
* @param {Any} invokeParam - 传递给过滤函数的参数
* @returns {Array|null} 符合条件的节点数组或单个节点如果没有符合条件的节点则返回空数组或null
*/
getNodesByFilter: function (setting, nodes, filter, isSingle, invokeParam) {
// 如果节点数组为空根据isSingle决定返回null还是空数组
if (!nodes) return (isSingle ? null : []);
// 初始化结果变量根据isSingle决定是null还是空数组
var result = isSingle ? null : [];
// 遍历所有节点
for (var i = 0, l = nodes.length; i < l; i++) {
var node = nodes[i];
// 使用过滤函数检查当前节点是否符合条件
if (tools.apply(filter, [node, invokeParam], false)) {
// 如果只需要一个节点且找到符合条件的节点,直接返回该节点
if (isSingle) {
return node;
}
// 否则将符合条件的节点加入结果数组
result.push(node);
}
// 获取当前节点的子节点
var children = data.nodeChildren(setting, node);
// 递归调用自身以处理子节点
var tmpResult = data.getNodesByFilter(setting, children, filter, isSingle, invokeParam);
// 如果只需要一个节点且在子节点中找到符合条件的节点,直接返回该节点
if (isSingle && !!tmpResult) {
return tmpResult;
}
// 根据isSingle决定是替换结果还是合并结果
result = isSingle ? tmpResult : result.concat(tmpResult);
}
// 返回最终的结果数组或单个节点
return result;
},
getPreNode: function (setting, node) {
// 如果节点为空返回null
if (!node) return null;
// 获取父节点,如果当前节点没有父节点则获取根节点
var p = node.parentTId ? node.getParentNode() : data.getRoot(setting),
// 获取父节点的子节点列表
children = data.nodeChildren(setting, p);
// 遍历子节点列表
for (var i = 0, l = children.length; i < l; i++) {
// 如果找到当前节点
if (children[i] === node) {
// 返回前一个节点如果是第一个节点则返回null
return (i == 0 ? null : children[i - 1]);
}
}
// 如果没有找到当前节点返回null
return null;
},
/**
* 获取树的根节点
* @param {Object} setting - 配置对象
* @returns {Object|null} 根节点或null
*/
getRoot: function (setting) {
// 根据配置对象的treeId获取根节点
return setting ? roots[setting.treeId] : null;
},
/**
* 获取所有树的根节点集合
* @returns {Object} 根节点集合
*/
getRoots: function () {
// 返回所有根节点的集合
return roots;
},
/**
* 获取指定树的配置对象
* @param {string} treeId - 树的唯一标识符
* @returns {Object|null} 配置对象或null
*/
getSetting: function (treeId) {
// 根据树的唯一标识符获取配置对象
return settings[treeId];
},
/**
* 获取所有树的配置对象集合
* @returns {Object} 配置对象集合
*/
getSettings: function () {
// 返回所有配置对象的集合
return settings;
},
/**
* 获取指定树的工具对象
* @param {string} treeId - 树的唯一标识符
* @returns {Object|null} 工具对象或null
*/
getZTreeTools: function (treeId) {
// 根据树的唯一标识符获取根节点
var r = this.getRoot(this.getSetting(treeId));
// 返回根节点的工具对象如果不存在则返回null
return r ? r.treeTools : null;
},
/**
* 初始化缓存
* @param {Object} setting - 配置对象
*/
initCache: function (setting) {
// 遍历并应用所有缓存初始化函数
for (var i = 0, j = _init.caches.length; i < j; i++) {
_init.caches[i].apply(this, arguments);
}
},
/**
* 初始化节点
* @param {Object} setting - 配置对象
* @param {number} level - 节点层级
* @param {Object} node - 当前节点
* @param {Object} parentNode - 父节点
* @param {Object} preNode - 前一个兄弟节点
* @param {Object} nextNode - 后一个兄弟节点
*/
initNode: function (setting, level, node, parentNode, preNode, nextNode) {
// 遍历并应用所有节点初始化函数
for (var i = 0, j = _init.nodes.length; i < j; i++) {
_init.nodes[i].apply(this, arguments);
}
},
initRoot: function (setting) {
// 遍历所有根节点初始化函数,并调用它们
for (var i = 0, j = _init.roots.length; i < j; i++) {
_init.roots[i].apply(this, arguments);
}
},
isSelectedNode: function (setting, node) {
// 获取当前设置的根节点
var root = data.getRoot(setting);
// 遍历当前选中的节点列表,检查目标节点是否在其中
for (var i = 0, j = root.curSelectedList.length; i < j; i++) {
if (node === root.curSelectedList[i]) return true; // 如果找到匹配的节点返回true
}
return false; // 如果没有找到匹配的节点返回false
},
nodeChildren: function (setting, node, newChildren) {
if (!node) {
return null; // 如果节点不存在返回null
}
var key = setting.data.key.children; // 获取子节点的键名
if (typeof newChildren !== 'undefined') {
node[key] = newChildren; // 如果提供了新的子节点,更新节点的子节点
}
return node[key]; // 返回节点的子节点
},
nodeIsParent: function (setting, node, newIsParent) {
if (!node) {
return false; // 如果节点不存在返回false
}
var key = setting.data.key.isParent;
// 检查 newIsParent 是否定义
if (typeof newIsParent !== 'undefined') {
// 如果 newIsParent 是字符串类型,则将其与 "true" 比较并转换为布尔值
if (typeof newIsParent === "string") {
newIsParent = tools.eqs(newIsParent, "true");
}
// 将 newIsParent 转换为布尔值并赋值给 node[key]
newIsParent = !!newIsParent;
node[key] = newIsParent;
} else if (typeof node[key] == "string"){
// 如果 node[key] 是字符串类型,则将其与 "true" 比较并转换为布尔值
node[key] = tools.eqs(node[key], "true");
} else {
// 将 node[key] 转换为布尔值
node[key] = !!node[key];
}
// 返回 node[key] 的布尔值
return node[key];
},
/**
* 设置或获取节点的名称
* @param {Object} setting - 配置对象
* @param {Object} node - 节点对象
* @param {String} [newName] - 新的节点名称(可选)
* @returns {String} - 节点名称
*/
nodeName: function (setting, node, newName) {
// 获取节点名称的键名
var key = setting.data.key.name;
// 如果 newName 已定义,则更新节点名称
if (typeof newName !== 'undefined') {
node[key] = newName;
}
// 将节点名称转换为字符串
var rawName = "" + node[key];
// 如果 render.name 是一个函数,则调用该函数并返回结果
if(typeof setting.data.render.name === 'function') {
return setting.data.render.name.call(this,rawName,node);
}
// 否则直接返回原始名称
return rawName;
},
nodeTitle: function (setting, node) {
// 获取节点标题,如果标题为空则使用名称
var t = setting.data.key.title === "" ? setting.data.key.name : setting.data.key.title;
// 将节点的标题转换为字符串
var rawTitle = "" + node[t];
// 如果设置了自定义渲染函数,则调用该函数渲染标题
if(typeof setting.data.render.title === 'function') {
return setting.data.render.title.call(this,rawTitle,node);
}
// 返回原始标题
return rawTitle;
},
removeNodeCache: function (setting, node) {
// 获取节点的子节点
var children = data.nodeChildren(setting, node);
// 如果存在子节点,递归删除每个子节点的缓存
if (children) {
for (var i = 0, l = children.length; i < l; i++) {
data.removeNodeCache(setting, children[i]);
}
}
// 将当前节点的缓存设置为null
data.getCache(setting).nodes[data.getNodeCacheId(node.tId)] = null;
},
removeSelectedNode: function (setting, node) {
// 获取树的根节点
var root = data.getRoot(setting);
// 遍历当前选中的节点列表
for (var i = 0, j = root.curSelectedList.length; i < j; i++) {
// 如果当前节点在选中列表中或其缓存不存在,则从选中列表中移除
if (node === root.curSelectedList[i] || !data.getNodeCache(setting, root.curSelectedList[i].tId)) {
root.curSelectedList.splice(i, 1);
// 触发取消选中事件
setting.treeObj.trigger(consts.event.UNSELECTED, [setting.treeId, node]);
// 调整索引以反映已移除的元素
i--;
j--;
}
}
},
setCache: function (setting, cache) {
// 将缓存对象存储到caches数组中键为treeId
caches[setting.treeId] = cache;
},
setRoot: function (setting, root) {
// 将根节点对象存储到roots数组中键为treeId
roots[setting.treeId] = root;
},
setZTreeTools: function (setting, zTreeTools) {
// 遍历_init.zTreeTools数组并调用每个工具函数传递当前上下文和参数
for (var i = 0, j = _init.zTreeTools.length; i < j; i++) {
_init.zTreeTools[i].apply(this, arguments);
}
},
transformToArrayFormat: function (setting, nodes) {
// 如果nodes为空返回空数组
if (!nodes) return [];
// 初始化结果数组r
var r = [];
// 如果nodes是数组则遍历每个节点并调用_do函数处理
if (tools.isArray(nodes)) {
for (var i = 0, l = nodes.length; i < l; i++) {
var node = nodes[i];
_do(node);
}
} else {
// 如果nodes不是数组直接调用_do函数处理
_do(nodes);
}
// 返回结果数组r
return r;
function _do(_node) {
// 将当前节点添加到结果数组中
r.push(_node);
// 获取当前节点的子节点
var children = data.nodeChildren(setting, _node);
// 如果存在子节点,则将其转换为数组格式并合并到结果数组中
if (children) {
r = r.concat(data.transformToArrayFormat(setting, children));
}
}
},
transformTozTreeFormat: function (setting, sNodes) {
// 定义变量i和l用于循环控制
var i, l,
// 获取节点ID的键名
key = setting.data.simpleData.idKey,
// 获取父节点ID的键名
parentKey = setting.data.simpleData.pIdKey;
// 如果key为空或sNodes不存在返回空数组
if (!key || key == "" || !sNodes) return [];
// 如果sNodes是数组类型
if (tools.isArray(sNodes)) {
// 初始化结果数组r
var r = [];
// 初始化临时映射对象tmpMap
var tmpMap = {};
// 遍历sNodes数组
for (i = 0, l = sNodes.length; i < l; i++) {
// 将每个节点按其ID存入tmpMap中
tmpMap[sNodes[i][key]] = sNodes[i];
}
// 遍历sNodes数组中的每一个元素
for (i = 0, l = sNodes.length; i < l; i++) {
// 获取当前节点的父节点在tmpMap中的引用
var p = tmpMap[sNodes[i][parentKey]];
// 如果父节点存在且当前节点不是其父节点
if (p && sNodes[i][key] != sNodes[i][parentKey]) {
// 获取父节点的子节点列表
var children = data.nodeChildren(setting, p);
// 如果父节点没有子节点列表,则初始化一个空列表
if (!children) {
children = data.nodeChildren(setting, p, []);
}
// 将当前节点添加到父节点的子节点列表中
children.push(sNodes[i]);
} else {
// 如果父节点不存在或当前节点是其父节点则将当前节点添加到结果数组r中
r.push(sNodes[i]);
}
}
// 如果条件满足返回变量r
return r;
} else {
// 如果条件不满足返回包含sNodes的数组
return [sNodes];
}
}
},
//method of event proxy
event = {
// 绑定事件的方法,接受一个设置参数
bindEvent: function (setting) {
// 遍历_init.bind数组中的每一个元素
for (var i = 0, j = _init.bind.length; i < j; i++) {
// 调用当前元素的apply方法将当前上下文和传入的参数传递给它
_init.bind[i].apply(this, arguments);
}
},
unbindEvent: function (setting) {
// 遍历_init.unbind数组并调用每个元素的apply方法将当前上下文和参数传递给它
for (var i = 0, j = _init.unbind.length; i < j; i++) {
_init.unbind[i].apply(this, arguments);
}
},
bindTree: function (setting) {
// 创建一个包含treeId属性的对象eventParam
var eventParam = {
treeId: setting.treeId
},
// 获取树对象
o = setting.treeObj;
// 如果视图中不允许选择文本
if (!setting.view.txtSelectedEnable) {
// 绑定selectstart事件处理程序防止文本被选中并设置CSS样式以禁用文本选择
o.bind('selectstart', handler.onSelectStart).css({
"-moz-user-select": "-moz-none"
});
}
// 绑定点击事件到对象o并使用event.proxy作为事件处理函数
o.bind('click', eventParam, event.proxy);
// 绑定双击事件到对象o并使用event.proxy作为事件处理函数
o.bind('dblclick', eventParam, event.proxy);
// 绑定鼠标悬停事件到对象o并使用event.proxy作为事件处理函数
o.bind('mouseover', eventParam, event.proxy);
// 绑定鼠标移出事件到对象o并使用event.proxy作为事件处理函数
o.bind('mouseout', eventParam, event.proxy);
// 绑定鼠标按下事件到对象o并使用event.proxy作为事件处理函数
o.bind('mousedown', eventParam, event.proxy);
// 绑定鼠标抬起事件到对象o并使用event.proxy作为事件处理函数
o.bind('mouseup', eventParam, event.proxy);
// 绑定右键菜单事件到对象o并使用event.proxy作为事件处理函数
o.bind('contextmenu', eventParam, event.proxy);
unbindTree: function (setting) {
// 获取树形对象的引用
var o = setting.treeObj;
// 解绑'selectstart'事件,防止文本选择
o.unbind('selectstart', handler.onSelectStart)
// 解绑'click'事件,取消点击事件的代理处理
.unbind('click', event.proxy)
// 解绑'dblclick'事件,取消双击事件的代理处理
.unbind('dblclick', event.proxy)
// 解绑'mouseover'事件,取消鼠标悬停事件的代理处理
.unbind('mouseover', event.proxy)
// 解绑'mouseout'事件,取消鼠标移出事件的代理处理
.unbind('mouseout', event.proxy)
// 解绑'mousedown'事件,取消鼠标按下事件的代理处理
.unbind('mousedown', event.proxy)
// 解绑'mouseup'事件,取消鼠标抬起事件的代理处理
.unbind('mouseup', event.proxy)
// 解绑'contextmenu'事件,取消右键菜单事件的代理处理
.unbind('contextmenu', event.proxy);
},
doProxy: function (e) {
// 初始化一个空数组,用于存储代理函数的返回结果
var results = [];
// 遍历所有代理函数
for (var i = 0, j = _init.proxys.length; i < j; i++) {
// 调用当前代理函数并将结果存储在proxyResult中
var proxyResult = _init.proxys[i].apply(this, arguments);
// 将当前代理函数的结果添加到results数组中
results.push(proxyResult);
// 如果当前代理函数的返回结果包含stop属性且为true则中断循环
if (proxyResult.stop) {
break;
}
}
// 返回所有代理函数的结果数组
return results;
},
proxy: function (e) {
// 获取当前树的设置信息
var setting = data.getSetting(e.data.treeId);
// 检查用户是否有权限执行该操作如果没有权限则返回true
if (!tools.uCanDo(setting, e)) return true;
// 执行代理事件并获取结果数组
var results = event.doProxy(e),
r = true, // 初始化返回值为true
x = false; // 初始化标志位为false
// 遍历所有代理结果
for (var i = 0, l = results.length; i < l; i++) {
var proxyResult = results[i];
// 如果存在节点事件回调函数,则调用它并更新返回值和标志位
if (proxyResult.nodeEventCallback) {
x = true;
r = proxyResult.nodeEventCallback.apply(proxyResult, [e, proxyResult.node]) && r;
}
// 如果存在树事件回调函数,则调用它并更新返回值和标志位
if (proxyResult.treeEventCallback) {
x = true;
r = proxyResult.treeEventCallback.apply(proxyResult, [e, proxyResult.node]) && r;
}
}
// 返回最终的结果值
return r;
}
},
//method of event handler
handler = {
// 处理节点切换事件的方法
onSwitchNode: function (event, node) {
// 获取当前树的设置信息
var setting = data.getSetting(event.data.treeId);
// 如果节点是展开状态
if (node.open) {
// 调用回调函数判断是否允许折叠节点如果返回false则终止操作
if (tools.apply(setting.callback.beforeCollapse, [setting.treeId, node], true) == false) return true;
// 设置根节点的expandTriggerFlag为true表示触发了展开操作
data.getRoot(setting).expandTriggerFlag = true;
// 切换节点的状态(从展开到折叠)
view.switchNode(setting, node);
} else {
// 调用回调函数判断是否允许展开节点如果返回false则终止操作
if (tools.apply(setting.callback.beforeExpand, [setting.treeId, node], true) == false) return true;
// 设置根节点的expandTriggerFlag为true表示触发了展开操作
data.getRoot(setting).expandTriggerFlag = true;
// 切换节点的状态(从折叠到展开)
view.switchNode(setting, node);
}
// 返回true表示操作成功
return true;
},
/**
* 处理节点点击事件的函数
* @param {Object} event - 事件对象,包含触发事件的信息
* @param {Object} node - 被点击的节点对象
*/
onClickNode: function (event, node) {
// 获取当前树的设置信息
var setting = data.getSetting(event.data.treeId),
// 根据是否按下Ctrl或Meta键以及节点是否已选中来确定点击标志
clickFlag = ((setting.view.autoCancelSelected && (event.ctrlKey || event.metaKey)) && data.isSelectedNode(setting, node)) ? 0 : (setting.view.autoCancelSelected && (event.ctrlKey || event.metaKey) && setting.view.selectedMulti) ? 2 : 1;
// 如果beforeClick回调函数返回false则终止执行并返回true
if (tools.apply(setting.callback.beforeClick, [setting.treeId, node, clickFlag], true) == false) return true;
// 如果点击标志为0取消之前选中的节点
if (clickFlag === 0) {
view.cancelPreSelectedNode(setting, node);
} else {
// 否则根据点击标志选择或取消选择节点
view.selectNode(setting, node, clickFlag === 2);
}
// 触发CLICK事件传递事件对象、树ID、节点和点击标志
setting.treeObj.trigger(consts.event.CLICK, [event, setting.treeId, node, clickFlag]);
// 返回true表示事件处理完成
return true;
},
onZTreeMousedown: function (event, node) {
// 获取当前树的设置信息
var setting = data.getSetting(event.data.treeId);
// 在鼠标按下事件之前执行回调函数如果返回true则继续执行后续操作
if (tools.apply(setting.callback.beforeMouseDown, [setting.treeId, node], true)) {
// 执行鼠标按下事件的回调函数
tools.apply(setting.callback.onMouseDown, [event, setting.treeId, node]);
}
return true; // 始终返回true表示事件处理完成
},
onZTreeMouseup: function (event, node) {
// 获取当前树的设置信息
var setting = data.getSetting(event.data.treeId);
// 在鼠标抬起事件之前执行回调函数如果返回true则继续执行后续操作
if (tools.apply(setting.callback.beforeMouseUp, [setting.treeId, node], true)) {
// 执行鼠标抬起事件的回调函数
tools.apply(setting.callback.onMouseUp, [event, setting.treeId, node]);
}
return true; // 始终返回true表示事件处理完成
},
onZTreeDblclick: function (event, node) {
// 获取当前树的设置信息
var setting = data.getSetting(event.data.treeId);
// 在双击事件之前执行回调函数如果返回true则继续执行后续操作
if (tools.apply(setting.callback.beforeDblClick, [setting.treeId, node], true)) {
// 执行双击事件的回调函数
tools.apply(setting.callback.onDblClick, [event, setting.treeId, node]);
}
return true; // 始终返回true表示事件处理完成
},
onZTreeContextmenu: function (event, node) {
// 获取当前树的设置信息
var setting = data.getSetting(event.data.treeId);
// 在右键点击事件之前执行回调函数如果返回true则继续执行后续操作
if (tools.apply(setting.callback.beforeRightClick, [setting.treeId, node], true)) {
// 执行右键点击事件的回调函数
tools.apply(setting.callback.onRightClick, [event, setting.treeId, node]);
}
// 检查是否有定义右键点击的回调函数如果没有则返回false
return (typeof setting.callback.onRightClick) != "function";
},
onSelectStart: function (e) {
// 获取触发事件的元素节点名称并转换为小写
var n = e.originalEvent.srcElement.nodeName.toLowerCase();
// 如果元素是input或textarea则允许选择文本
return (n === "input" || n === "textarea");
}
},
//method of tools for zTree
tools = {
/**
* 应用函数,如果传入的参数是函数则调用该函数并返回结果,否则返回默认值。
* @param {Function} fun - 要应用的函数
* @param {Array} param - 函数参数数组
* @param {*} defaultValue - 默认值
* @returns {*} 函数执行结果或默认值
*/
apply: function (fun, param, defaultValue) {
if ((typeof fun) == "function") {
// 如果fun是函数则使用apply方法调用它
return fun.apply(zt, param ? param : []);
}
// 如果fun不是函数返回默认值
return defaultValue;
},
/**
* 判断节点是否可以异步加载子节点。
* @param {Object} setting - 配置对象
* @param {Object} node - 当前节点
* @returns {boolean} 是否可以异步加载子节点
*/
canAsync: function (setting, node) {
// 获取节点的子节点
var children = data.nodeChildren(setting, node);
// 判断节点是否是父节点
var isParent = data.nodeIsParent(setting, node);
// 判断是否启用了异步加载,并且节点存在且是父节点,同时没有异步标记且没有子节点
return (setting.async.enable && node && isParent && !(node.zAsync || (children && children.length > 0)));
},
/**
* 深度克隆一个对象。
* @param {Object} obj - 要克隆的对象
* @returns {Object} 克隆后的新对象
*/
clone: function (obj) {
if (obj === null) return null; // 如果对象为null直接返回null
// 根据对象类型创建新对象
var o = tools.isArray(obj) ? [] : {};
for (var i in obj) {
// 递归克隆对象属性
o[i] = (obj[i] instanceof Date) ? new Date(obj[i].getTime()) : (typeof obj[i] === "object" ? tools.clone(obj[i]) : obj[i]);
}
return o; // 返回克隆后的对象
},
/**
* 比较两个字符串是否相等(忽略大小写)。
* @param {string} str1 - 第一个字符串
* @param {string} str2 - 第二个字符串
* @returns {boolean} 字符串是否相等
*/
eqs: function (str1, str2) {
return str1.toLowerCase() === str2.toLowerCase(); // 转换为小写后比较
},
/**
* 判断一个对象是否是数组。
* @param {Object} arr - 要判断的对象
* @returns {boolean} 是否是数组
*/
isArray: function (arr) {
return Object.prototype.toString.apply(arr) === "[object Array]"; // 通过原型链判断是否为数组
},
/**
* 判断一个对象是否是HTML元素。
* @param {Object} o - 要判断的对象
* @returns {boolean} 是否是HTML元素
*/
isElement: function (o) {
return (
typeof HTMLElement === "object" ? o instanceof HTMLElement : //DOM2
o && typeof o === "object" && o !== null && o.nodeType === 1 && typeof o.nodeName === "string" //DOM3
);
},
$: function (node, exp, setting) {
// 检查exp是否存在且不是字符串类型
if (!!exp && typeof exp != "string") {
// 如果exp存在且不是字符串则将setting赋值为exp并将exp置为空字符串
setting = exp;
exp = "";
}
// 检查node是否为字符串类型
if (typeof node == "string") {
// 如果node是字符串则根据setting中的treeObj获取文档对象并返回相应的jQuery对象
return $(node, setting ? setting.treeObj.get(0).ownerDocument : null);
} else {
// 如果node不是字符串则根据node的tId和exp构建选择器并根据setting中的treeObj返回相应的jQuery对象
return $("#" + node.tId + exp, setting ? setting.treeObj : null);
}
},
getMDom: function (setting, curDom, targetExpr) {
// 如果当前DOM节点为空返回null
if (!curDom) return null;
// 循环遍历父节点直到找到匹配的树ID或到达根节点
while (curDom && curDom.id !== setting.treeId) {
// 遍历目标表达式数组
for (var i = 0, l = targetExpr.length; curDom.tagName && i < l; i++) {
// 检查当前DOM节点的标签名和属性是否匹配目标表达式
if (tools.eqs(curDom.tagName, targetExpr[i].tagName) && curDom.getAttribute(targetExpr[i].attrName) !== null) {
// 如果匹配返回当前DOM节点
return curDom;
}
}
// 移动到父节点继续检查
curDom = curDom.parentNode;
}
// 如果没有找到匹配的节点返回null
return null;
},
getNodeMainDom: function (target) {
// 获取目标元素的父级<li>元素,如果不存在则获取最近的祖先<li>元素
return ($(target).parent("li").get(0) || $(target).parentsUntil("li").parent().get(0));
},
isChildOrSelf: function (dom, parentId) {
// 判断给定的DOM节点是否是指定父节点的子节点或自身
return ($(dom).closest("#" + parentId).length > 0);
},
uCanDo: function (setting, e) {
// 默认返回true表示可以执行操作
return true;
}
//method of operate ztree dom
view = {
// 添加节点的方法
addNodes: function (setting, parentNode, index, newNodes, isSilent) {
// 判断父节点是否是父节点类型
var isParent = data.nodeIsParent(setting, parentNode);
// 如果配置中要求保持叶子节点,并且父节点存在且不是父节点类型,则直接返回
if (setting.data.keep.leaf && parentNode && !isParent) {
return;
}
// 如果新节点不是数组,则将其转换为数组
if (!tools.isArray(newNodes)) {
newNodes = [newNodes];
}
// 如果启用了简单数据格式则将新节点转换为zTree格式
if (setting.data.simpleData.enable) {
newNodes = data.transformTozTreeFormat(setting, newNodes);
}
// 如果父节点存在
if (parentNode) {
// 获取父节点的开关对象、图标对象和子节点容器对象
var target_switchObj = $$(parentNode, consts.id.SWITCH, setting),
target_icoObj = $$(parentNode, consts.id.ICON, setting),
target_ulObj = $$(parentNode, consts.id.UL, setting);
// 如果父节点未打开
if (!parentNode.open) {
// 替换父节点的开关类为关闭状态
view.replaceSwitchClass(parentNode, target_switchObj, consts.folder.CLOSE);
// 替换父节点的图标类为关闭状态
view.replaceIcoClass(parentNode, target_icoObj, consts.folder.CLOSE);
// 设置父节点为关闭状态
parentNode.open = false;
// 隐藏父节点的子节点容器
target_ulObj.css({
"display": "none"
});
}
data.addNodesData(setting, parentNode, index, newNodes);
// 将新节点数据添加到父节点中
view.createNodes(setting, parentNode.level + 1, newNodes, parentNode, index);
// 创建并显示新节点,设置其层级为父节点的层级加一
if (!isSilent) {
view.expandCollapseParentNode(setting, parentNode, true);
}
// 如果不需要静默处理,则展开父节点以显示新添加的子节点
} else {
data.addNodesData(setting, data.getRoot(setting), index, newNodes);
// 如果没有父节点,则将新节点数据添加到根节点中
view.createNodes(setting, 0, newNodes, null, index);
// 创建并显示新节点设置其层级为0根节点
}
},
appendNodes: function (setting, level, nodes, parentNode, index, initFlag, openFlag) {
if (!nodes) return [];
// 如果节点列表为空,返回空数组
var html = [];
// 初始化HTML数组用于存储生成的HTML代码片段
var tmpPNode = (parentNode) ? parentNode : data.getRoot(setting),
// 获取父节点,如果未指定父节点,则使用根节点
tmpPChild = data.nodeChildren(setting, tmpPNode),
// 获取父节点的子节点列表
isFirstNode, isLastNode;
// 定义两个变量,用于标记是否是第一个或最后一个节点
if (!tmpPChild || index >= tmpPChild.length - nodes.length) {
index = -1;
}
// 如果父节点没有子节点,或者索引超出范围,则将索引设置为-1表示在末尾追加节点
// 遍历节点数组
for (var i = 0, l = nodes.length; i < l; i++) {
// 获取当前节点
var node = nodes[i];
// 如果初始化标志为真
if (initFlag) {
// 判断是否为第一个节点
isFirstNode = ((index === 0 || tmpPChild.length == nodes.length) && (i == 0));
// 判断是否为最后一个节点
isLastNode = (index < 0 && i == (nodes.length - 1));
// 初始化节点
data.initNode(setting, level, node, parentNode, isFirstNode, isLastNode, openFlag);
// 将节点添加到缓存中
data.addNodeCache(setting, node);
}
// 检查节点是否为父节点
var isParent = data.nodeIsParent(setting, node);
// 存储子节点的HTML内容
var childHtml = [];
// 获取子节点
var children = data.nodeChildren(setting, node);
// 如果存在子节点
if (children && children.length > 0) {
// 先生成子节点的HTML因为需要检查类型
childHtml = view.appendNodes(setting, level + 1, children, node, -1, initFlag, openFlag && node.open);
}
// 如果打开标志为真
if (openFlag) {
// 生成主节点前的DOM结构
view.makeDOMNodeMainBefore(html, setting, node);
// 生成节点连接线
view.makeDOMNodeLine(html, setting, node);
// 获取节点前的内容
data.getBeforeA(setting, node, html);
// 生成节点名称前的DOM结构
view.makeDOMNodeNameBefore(html, setting, node);
// 获取节点内部前的内容
data.getInnerBeforeA(setting, node, html);
// 生成节点图标
view.makeDOMNodeIcon(html, setting, node);
// 获取节点内部后的内容
data.getInnerAfterA(setting, node, html);
// 生成节点名称后的DOM结构
view.makeDOMNodeNameAfter(html, setting, node);
// 获取节点后的内容
data.getAfterA(setting, node, html);
// 如果节点是父节点且已打开
if (isParent && node.open) {
// 生成包含子节点的UL HTML结构
view.makeUlHtml(setting, node, html, childHtml.join(''));
}
// 生成主节点后的DOM结构
view.makeDOMNodeMainAfter(html, setting, node);
// 将创建的节点添加到已创建节点列表中
data.addCreatedNode(setting, node);
}
}
return html;
},
/**
* 将父节点的UL DOM元素追加到指定的节点上
* @param {Object} setting - 配置对象
* @param {Object} node - 当前节点对象
*/
appendParentULDom: function (setting, node) {
// 初始化HTML数组
var html = [],
// 获取当前节点对应的jQuery对象
nObj = $$(node, setting);
// 如果当前节点不存在且有父节点ID则递归调用自身
if (!nObj.get(0) && !!node.parentTId) {
view.appendParentULDom(setting, node.getParentNode());
nObj = $$(node, setting);
}
// 获取当前节点对应的UL jQuery对象
var ulObj = $$(node, consts.id.UL, setting);
// 如果UL对象存在则移除它
if (ulObj.get(0)) {
ulObj.remove();
}
// 获取子节点数据
var children = data.nodeChildren(setting, node),
// 生成子节点的HTML内容
childHtml = view.appendNodes(setting, node.level + 1, children, node, -1, false, true);
// 生成UL HTML内容并追加到html数组中
view.makeUlHtml(setting, node, html, childHtml.join(''));
// 将生成的HTML内容追加到当前节点对象中
nObj.append(html.join(''));
},
/**
* 异步加载节点数据
* @param {Object} setting - 配置对象
* @param {Object} node - 当前节点对象
* @param {Boolean} isSilent - 是否静默加载
* @param {Function} callback - 回调函数
*/
asyncNode: function (setting, node, isSilent, callback) {
var i, l;
// 判断当前节点是否是父节点
var isParent = data.nodeIsParent(setting, node);
// 如果当前节点不是父节点直接执行回调函数并返回false
if (node && !isParent) {
tools.apply(callback);
return false;
// 如果当前节点正在异步加载直接返回false
} else if (node && node.isAjaxing) {
return false;
// 如果beforeAsync回调函数返回false则执行回调函数并返回false
} else if (tools.apply(setting.callback.beforeAsync, [setting.treeId, node], true) == false) {
tools.apply(callback);
return false;
}
if (node) {
// 如果节点存在设置节点的isAjaxing属性为true表示该节点正在进行异步请求
node.isAjaxing = true;
// 获取节点对应的图标对象
var icoObj = $$(node, consts.id.ICON, setting);
// 更新图标对象的样式和类名,显示加载中的动画效果
icoObj.attr({"style": "", "class": consts.className.BUTTON + " " + consts.className.ICO_LOADING});
}
// 初始化一个空对象,用于存储参数
var tmpParam = {};
// 调用工具函数生成自动参数数组
var autoParam = tools.apply(setting.async.autoParam, [setting.treeId, node], setting.async.autoParam);
// 遍历自动参数数组
for (i = 0, l = autoParam.length; node && i < l; i++) {
// 将每个参数按等号分割成键值对
var pKey = autoParam[i].split("="), spKey = pKey;
if (pKey.length > 1) {
// 如果参数包含等号,则取等号后面的部分作为键
spKey = pKey[1];
// 取等号前面的部分作为值
pKey = pKey[0];
}
// 将节点的对应属性值赋给临时参数对象
tmpParam[spKey] = node[pKey];
}
// 调用工具函数生成其他参数数组
var otherParam = tools.apply(setting.async.otherParam, [setting.treeId, node], setting.async.otherParam);
// 如果其他参数是数组形式
if (tools.isArray(otherParam)) {
// 遍历数组,以键值对的形式添加到临时参数对象中
for (i = 0, l = otherParam.length; i < l; i += 2) {
tmpParam[otherParam[i]] = otherParam[i + 1];
}
} else {
// 如果其他参数是对象形式
// 遍历对象,将每个属性添加到临时参数对象中
for (var p in otherParam) {
tmpParam[p] = otherParam[p];
}
}
// 获取当前根节点的版本号
var _tmpV = data.getRoot(setting)._ver;
// 发起异步请求
$.ajax({
// 设置请求的内容类型
contentType: setting.async.contentType,
// 禁用缓存
cache: false,
// 设置请求的类型GET、POST等
type: setting.async.type,
// 生成请求的URL使用工具函数处理模板字符串
url: tools.apply(setting.async.url, [setting.treeId, node], setting.async.url),
// 根据内容类型决定发送的数据格式
data: setting.async.contentType.indexOf('application/json') > -1 ? JSON.stringify(tmpParam) : tmpParam,
// 设置期望的响应数据类型
dataType: setting.async.dataType,
// 设置请求头信息
headers: setting.async.headers,
// 设置XHR字段
xhrFields: setting.async.xhrFields,
// 请求成功时的回调函数
success: function (msg) {
// 如果版本号发生变化,则直接返回,不进行后续处理
if (_tmpV != data.getRoot(setting)._ver) {
return;
}
// 初始化新节点数组
var newNodes = [];
try {
// 如果响应为空或长度为0则新节点数组为空
if (!msg || msg.length == 0) {
newNodes = [];
// 如果响应是字符串则尝试解析为JSON对象
} else if (typeof msg == "string") {
newNodes = eval("(" + msg + ")");
// 否则直接将响应赋值给新节点数组
} else {
newNodes = msg;
}
} catch (err) {
// 如果解析过程中发生错误,则将原始响应赋值给新节点数组
newNodes = msg;
}
if (node) {
// 将节点的isAjaxing属性设置为null表示该节点不再进行异步操作
node.isAjaxing = null;
// 设置节点的zAsync属性为true表示该节点是异步加载的
node.zAsync = true;
}
// 更新节点的图标和线条样式
view.setNodeLineIcos(setting, node);
if (newNodes && newNodes !== "") {
// 应用数据过滤器处理新节点数据
newNodes = tools.apply(setting.async.dataFilter, [setting.treeId, node, newNodes], newNodes);
// 添加新节点到视图中
view.addNodes(setting, node, -1, !!newNodes ? tools.clone(newNodes) : [], !!isSilent);
} else {
// 如果新节点数据为空,则添加空数组作为新节点
view.addNodes(setting, node, -1, [], !!isSilent);
}
// 触发异步成功事件
setting.treeObj.trigger(consts.event.ASYNC_SUCCESS, [setting.treeId, node, msg]);
// 执行回调函数
tools.apply(callback);
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
// 如果版本号不一致,直接返回
if (_tmpV != data.getRoot(setting)._ver) {
return;
}
// 将节点的isAjaxing属性设置为null表示该节点不再进行异步操作
if (node) node.isAjaxing = null;
// 更新节点的图标和线条样式
view.setNodeLineIcos(setting, node);
// 触发异步错误事件
setting.treeObj.trigger(consts.event.ASYNC_ERROR, [setting.treeId, node, XMLHttpRequest, textStatus, errorThrown]);
}
},
return true;
},
cancelPreSelectedNode: function (setting, node, excludeNode) {
// 获取当前选中的节点列表
var list = data.getRoot(setting).curSelectedList,
i, n;
// 从后向前遍历选中的节点列表
for (i = list.length - 1; i >= 0; i--) {
n = list[i];
// 如果当前节点是需要取消选中的节点或排除节点,则进行处理
if (node === n || (!node && (!excludeNode || excludeNode !== n))) {
// 移除节点的选中样式
$$(n, consts.id.A, setting).removeClass(consts.node.CURSELECTED);
if (node) {
// 从选中列表中移除该节点
data.removeSelectedNode(setting, node);
break;
} else {
// 从选中列表中删除该节点并触发取消选中事件
list.splice(i, 1);
setting.treeObj.trigger(consts.event.UNSELECTED, [setting.treeId, n]);
}
}
}
},
/**
* 创建节点回调函数
* @param {Object} setting - 配置对象,包含回调和视图设置
*/
createNodeCallback: function (setting) {
// 检查是否有自定义的节点创建回调或添加自定义DOM的方法
if (!!setting.callback.onNodeCreated || !!setting.view.addDiyDom) {
// 获取根节点数据
var root = data.getRoot(setting);
// 当根节点的已创建节点列表不为空时循环处理
while (root.createdNodes.length > 0) {
// 从已创建节点列表中移除并获取第一个节点
var node = root.createdNodes.shift();
// 调用视图中的添加自定义DOM方法
tools.apply(setting.view.addDiyDom, [setting.treeId, node]);
// 如果存在节点创建回调,则触发该回调事件
if (!!setting.callback.onNodeCreated) {
setting.treeObj.trigger(consts.event.NODECREATED, [setting.treeId, node]);
}
}
}
},
createNodes: function (setting, level, nodes, parentNode, index) {
// 如果节点为空或长度为0则直接返回
if (!nodes || nodes.length == 0) return;
// 获取根节点
var root = data.getRoot(setting),
// 判断是否打开父节点的标志
openFlag = !parentNode || parentNode.open || !!$$(data.nodeChildren(setting, parentNode)[0], setting).get(0);
// 初始化创建的节点数组
root.createdNodes = [];
// 生成节点的HTML并追加到视图中
var zTreeHtml = view.appendNodes(setting, level, nodes, parentNode, index, true, openFlag),
parentObj, nextObj;
// 如果没有父节点则将生成的HTML追加到树对象中
if (!parentNode) {
parentObj = setting.treeObj;
//setting.treeObj.append(zTreeHtml.join(''));
} else {
// 获取父节点的UL对象
var ulObj = $$(parentNode, consts.id.UL, setting);
if (ulObj.get(0)) {
parentObj = ulObj;
//ulObj.append(zTreeHtml.join(''));
}
}
// 如果父对象存在
if (parentObj) {
// 如果索引大于等于0则获取下一个兄弟节点
if (index >= 0) {
nextObj = parentObj.children()[index];
}
// 如果索引大于等于0且下一个兄弟节点存在则在该节点之前插入生成的HTML
if (index >= 0 && nextObj) {
$(nextObj).before(zTreeHtml.join(''));
} else {
// 否则直接在父对象中追加生成的HTML
parentObj.append(zTreeHtml.join(''));
}
}
view.createNodeCallback(setting);
},
destroy: function (setting) {
// 如果 setting 为空,直接返回
if (!setting) return;
// 初始化缓存
data.initCache(setting);
// 初始化根节点
data.initRoot(setting);
// 解绑树的事件
event.unbindTree(setting);
// 解绑其他事件
event.unbindEvent(setting);
// 清空树对象的内容
setting.treeObj.empty();
// 删除 settings 中对应的树设置
delete settings[setting.treeId];
},
expandCollapseNode: function (setting, node, expandFlag, animateFlag, callback) {
// 获取根节点
var root = data.getRoot(setting);
var tmpCb, _callback;
// 如果 node 为空,执行回调并返回
if (!node) {
tools.apply(callback, []);
return;
}
// 获取子节点
var children = data.nodeChildren(setting, node);
// 判断是否为父节点
var isParent = data.nodeIsParent(setting, node);
// 如果根节点的 expandTriggerFlag 为真
if (root.expandTriggerFlag) {
_callback = callback;
tmpCb = function () {
// 如果存在回调函数,则执行回调
if (_callback) _callback();
// 根据节点的 open 状态触发相应的事件
if (node.open) {
setting.treeObj.trigger(consts.event.EXPAND, [setting.treeId, node]);
} else {
setting.treeObj.trigger(consts.event.COLLAPSE, [setting.treeId, node]);
}
};
callback = tmpCb;
// 将临时回调函数赋值给回调变量
root.expandTriggerFlag = false;
// 重置根节点的展开触发标志
}
if (!node.open && isParent && ((!$$(node, consts.id.UL, setting).get(0)) || (children && children.length > 0 && !$$(children[0], setting).get(0)))) {
// 如果节点未打开且是父节点,并且没有子节点或子节点的第一个元素不存在
view.appendParentULDom(setting, node);
// 为父节点添加子节点的DOM结构
view.createNodeCallback(setting);
// 创建节点的回调函数
}
if (node.open == expandFlag) {
// 如果节点的打开状态与期望的展开标志相同
tools.apply(callback, []);
// 执行回调函数
return;
// 结束函数执行
}
var ulObj = $$(node, consts.id.UL, setting),
// 获取节点的UL对象
switchObj = $$(node, consts.id.SWITCH, setting),
// 获取节点的开关对象
icoObj = $$(node, consts.id.ICON, setting);
// 获取节点的图标对象
if (isParent) {
// 如果节点是父节点
node.open = !node.open;
// 切换节点的打开状态
if (node.iconOpen && node.iconClose) {
// 如果节点有打开和关闭的图标
icoObj.attr("style", view.makeNodeIcoStyle(setting, node));
// 根据节点的状态设置图标样式
}
if (node.open) {
// 如果节点是打开状态,替换开关类为打开状态的样式
view.replaceSwitchClass(node, switchObj, consts.folder.OPEN);
// 替换图标类为打开状态的样式
view.replaceIcoClass(node, icoObj, consts.folder.OPEN);
// 如果没有动画标志或展开速度为空,直接显示子节点
if (animateFlag == false || setting.view.expandSpeed == "") {
ulObj.show();
// 执行回调函数
tools.apply(callback, []);
} else {
// 如果有子节点,使用滑动效果展开子节点
if (children && children.length > 0) {
ulObj.slideDown(setting.view.expandSpeed, callback);
} else {
// 否则直接显示子节点
ulObj.show();
// 执行回调函数
tools.apply(callback, []);
}
}
} else {
// 如果节点是关闭状态,替换开关类为关闭状态的样式
view.replaceSwitchClass(node, switchObj, consts.folder.CLOSE);
// 替换图标类为关闭状态的样式
view.replaceIcoClass(node, icoObj, consts.folder.CLOSE);
// 如果没有动画标志、展开速度为空或没有子节点,直接隐藏子节点
if (animateFlag == false || setting.view.expandSpeed == "" || !(children && children.length > 0)) {
ulObj.hide();
// 执行回调函数
tools.apply(callback, []);
} else {
// 否则使用滑动效果隐藏子节点
ulObj.slideUp(setting.view.expandSpeed, callback);
}
}
} else {
// 如果节点不存在,直接执行回调函数
tools.apply(callback, []);
}
expandCollapseParentNode: function (setting, node, expandFlag, animateFlag, callback) {
// 如果节点不存在,直接返回
if (!node) return;
// 如果节点没有父节点ID则直接展开或折叠该节点
if (!node.parentTId) {
view.expandCollapseNode(setting, node, expandFlag, animateFlag, callback);
return;
} else {
// 否则先展开或折叠当前节点
view.expandCollapseNode(setting, node, expandFlag, animateFlag);
}
// 如果节点有父节点ID递归展开或折叠父节点
if (node.parentTId) {
view.expandCollapseParentNode(setting, node.getParentNode(), expandFlag, animateFlag, callback);
}
},
/**
* 展开或折叠子节点
* @param {Object} setting - 配置对象
* @param {Object} node - 当前节点
* @param {Boolean} expandFlag - 是否展开
* @param {Boolean} animateFlag - 是否带动画
* @param {Function} callback - 回调函数
*/
expandCollapseSonNode: function (setting, node, expandFlag, animateFlag, callback) {
// 获取根节点
var root = data.getRoot(setting),
// 获取子节点列表
treeNodes = (node) ? data.nodeChildren(setting, node) : data.nodeChildren(setting, root),
// 如果是根节点,则使用传入的动画标志,否则不使用动画
selfAnimateSign = (node) ? false : animateFlag,
// 保存当前的展开触发标志
expandTriggerFlag = data.getRoot(setting).expandTriggerFlag;
// 暂时禁用展开触发标志
data.getRoot(setting).expandTriggerFlag = false;
// 如果存在子节点列表,遍历并展开或折叠每个子节点
if (treeNodes) {
for (var i = 0, l = treeNodes.length; i < l; i++) {
if (treeNodes[i]) view.expandCollapseSonNode(setting, treeNodes[i], expandFlag, selfAnimateSign);
}
}
// 恢复原来的展开触发标志
data.getRoot(setting).expandTriggerFlag = expandTriggerFlag;
// 最后展开或折叠当前节点
view.expandCollapseNode(setting, node, expandFlag, animateFlag, callback);
},
/**
* 判断节点是否被选中
* @param {Object} setting - 配置对象
* @param {Object} node - 当前节点
* @returns {Boolean} - 是否被选中
*/
isSelectedNode: function (setting, node) {
// 如果节点不存在返回false
if (!node) {
return false;
}
// 获取当前选中的节点列表
var list = data.getRoot(setting).curSelectedList,
i;
// 遍历选中的节点列表,检查是否存在与当前节点相同的节点
for (i = list.length - 1; i >= 0; i--) {
if (node === list[i]) {
return true;
}
}
// 如果未找到匹配的节点返回false
return false;
},
makeDOMNodeIcon: function (html, setting, node) {
// 获取节点名称字符串
var nameStr = data.nodeName(setting, node),
// 根据设置决定是否将名称作为HTML显示否则进行转义处理
name = setting.view.nameIsHTML ? nameStr : nameStr.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
// 生成包含图标和名称的HTML片段并推入html数组中
html.push("<span id='", node.tId, consts.id.ICON,
"' title='' treeNode", consts.id.ICON, " class='", view.makeNodeIcoClass(setting, node),
"' style='", view.makeNodeIcoStyle(setting, node), "'></span><span id='", node.tId, consts.id.SPAN,
"' class='", consts.className.NAME,
"'>", name, "</span>");
},
makeDOMNodeLine: function (html, setting, node) {
// 生成节点线用于表示树结构的连接线的HTML片段并推入html数组中
html.push("<span id='", node.tId, consts.id.SWITCH, "' title='' class='", view.makeNodeLineClass(setting, node), "' treeNode", consts.id.SWITCH, "></span>");
},
makeDOMNodeMainAfter: function (html, setting, node) {
// 在主节点的HTML片段后添加结束标签</li>
html.push("</li>");
},
makeDOMNodeMainBefore: function (html, setting, node) {
// 在主节点的HTML片段前添加开始标签<li>,并设置相关属性
html.push("<li id='", node.tId, "' class='", consts.className.LEVEL, node.level, "' tabindex='0' hidefocus='true' treenode>");
},
makeDOMNodeNameAfter: function (html, setting, node) {
// 在节点名称的HTML片段后添加结束标签</a>
html.push("</a>");
},
makeDOMNodeNameBefore: function (html, setting, node) {
// 获取节点标题、URL、字体CSS样式和节点类名
var title = data.nodeTitle(setting, node),
url = view.makeNodeUrl(setting, node),
fontcss = view.makeNodeFontCss(setting, node),
nodeClasses = view.makeNodeClasses(setting, node),
fontStyle = [];
// 遍历字体CSS样式对象将其转换为字符串形式并推入fontStyle数组中
for (var f in fontcss) {
fontStyle.push(f, ":", fontcss[f], ";");
}
html.push("<a id='", node.tId, consts.id.A, "' class='", consts.className.LEVEL, node.level,
// 添加节点的额外类名
nodeClasses.add ? ' ' + nodeClasses.add.join(' ') : '',
"' treeNode", consts.id.A,
// 如果节点有点击事件则添加onclick属性
node.click ? " onclick=\"" + node.click + "\"" : "",
// 如果URL存在且长度大于0则添加href属性
((url != null && url.length > 0) ? " href='" + url + "'" : ""), " target='", view.makeNodeTarget(node), "' style='", fontStyle.join(''),
"'");
// 如果需要显示标题并且标题存在则添加title属性
if (tools.apply(setting.view.showTitle, [setting.treeId, node], setting.view.showTitle) && title) {
html.push("title='", title.replace(/'/g, "&#39;").replace(/</g, '&lt;').replace(/>/g, '&gt;'), "'");
}
html.push(">");
},
makeNodeFontCss: function (setting, node) {
// 获取节点的字体样式
var fontCss = tools.apply(setting.view.fontCss, [setting.treeId, node], setting.view.fontCss);
// 如果fontCss存在且不是函数则返回fontCss否则返回空对象
return (fontCss && ((typeof fontCss) != "function")) ? fontCss : {};
},
makeNodeClasses: function (setting, node) {
// 获取节点的类名
var classes = tools.apply(setting.view.nodeClasses, [setting.treeId, node], setting.view.nodeClasses);
// 如果classes存在且不是函数则返回classes否则返回默认的类名对象
return (classes && (typeof classes !== "function")) ? classes : {add:[], remove:[]};
},
makeNodeIcoClass: function (setting, node) {
// 初始化图标类名数组
var icoCss = ["ico"];
// 如果节点没有正在加载数据
if (!node.isAjaxing) {
// 判断节点是否是父节点
var isParent = data.nodeIsParent(setting, node);
// 根据节点是否有iconSkin设置前缀
icoCss[0] = (node.iconSkin ? node.iconSkin + "_" : "") + icoCss[0];
if (isParent) {
// 如果是父节点,根据节点是否展开添加相应的图标类名
icoCss.push(node.open ? consts.folder.OPEN : consts.folder.CLOSE);
} else {
// 如果不是父节点,添加文档图标类名
icoCss.push(consts.folder.DOCU);
}
}
// 返回拼接后的按钮类名和图标类名
return consts.className.BUTTON + " " + icoCss.join('_');
},
makeNodeIcoStyle: function (setting, node) {
// 初始化图标样式数组
var icoStyle = [];
// 如果节点没有进行Ajax请求
if (!node.isAjaxing) {
// 判断节点是否是父节点
var isParent = data.nodeIsParent(setting, node);
// 根据节点状态选择相应的图标
var icon = (isParent && node.iconOpen && node.iconClose) ? (node.open ? node.iconOpen : node.iconClose) : node[setting.data.key.icon];
// 如果存在图标,则添加背景图片样式
if (icon) icoStyle.push("background:url(", icon, ") 0 0 no-repeat;");
// 如果视图设置中不显示图标或应用showIcon函数返回false则隐藏图标
if (setting.view.showIcon == false || !tools.apply(setting.view.showIcon, [setting.treeId, node], true)) {
icoStyle.push("display:none;");
}
}
// 返回拼接后的图标样式字符串
return icoStyle.join('');
},
makeNodeLineClass: function (setting, node) {
// 初始化线条样式数组
var lineClass = [];
// 如果视图设置中显示线条
if (setting.view.showLine) {
// 根据节点层级和位置添加相应的线条样式
// 检查节点是否为根节点且是第一个和最后一个节点
if (node.level == 0 && node.isFirstNode && node.isLastNode) {
// 将根节点的样式类添加到lineClass数组中
lineClass.push(consts.line.ROOT);
} else if (node.level == 0 && node.isFirstNode) {
// 检查节点是否为根节点且是第一个节点
// 将根节点的第一个样式类添加到lineClass数组中
lineClass.push(consts.line.ROOTS);
} else if (node.isLastNode) {
// 检查节点是否是最后一个节点
// 将底部节点的样式类添加到lineClass数组中
lineClass.push(consts.line.BOTTOM);
} else {
// 默认情况下将中心节点的样式类添加到lineClass数组中
lineClass.push(consts.line.CENTER);
}
} else {
// 如果不显示线条,则添加无线条样式
lineClass.push(consts.line.NOLINE);
}
// 根据节点是否为父节点添加文件夹样式
if (data.nodeIsParent(setting, node)) {
lineClass.push(node.open ? consts.folder.OPEN : consts.folder.CLOSE);
} else {
lineClass.push(consts.folder.DOCU);
}
// 返回拼接后的线条样式字符串并调用makeNodeLineClassEx方法进一步处理
return view.makeNodeLineClassEx(node) + lineClass.join('_');
},
makeNodeLineClassEx: function (node) {
// 返回节点的按钮、层级和开关样式字符串
return consts.className.BUTTON + " " + consts.className.LEVEL + node.level + " " + consts.className.SWITCH + " ";
},
makeNodeTarget: function (node) {
// 返回节点的目标属性,如果不存在则默认为"_blank"
return (node.target || "_blank");
},
makeNodeUrl: function (setting, node) {
// 获取URL键值
var urlKey = setting.data.key.url;
// 返回节点的URL如果不存在则返回null
return node[urlKey] ? node[urlKey] : null;
},
makeUlHtml: function (setting, node, html, content) {
// 生成UL元素的HTML代码并根据节点状态设置显示或隐藏
html.push("<ul id='", node.tId, consts.id.UL, "' class='", consts.className.LEVEL, node.level, " ", view.makeUlLineClass(setting, node), "' style='display:", (node.open ? "block" : "none"), "'>");
// 插入内容到UL元素中
html.push(content);
// 关闭UL标签
html.push("</ul>");
},
makeUlLineClass: function (setting, node) {
// 如果视图显示线条且节点不是最后一个节点,则返回线条类名,否则返回空字符串
return ((setting.view.showLine && !node.isLastNode) ? consts.line.LINE : "");
},
removeChildNodes: function (setting, node) {
// 如果节点不存在,直接返回
if (!node) return;
// 获取节点的子节点列表
var nodes = data.nodeChildren(setting, node);
// 如果子节点列表不存在,直接返回
if (!nodes) return;
// 遍历所有子节点
for (var i = 0, l = nodes.length; i < l; i++) {
// 移除每个子节点的缓存
data.removeNodeCache(setting, nodes[i]);
}
data.removeSelectedNode(setting); // 移除选中的节点
delete node[setting.data.key.children]; // 删除节点的子节点属性
if (!setting.data.keep.parent) { // 如果设置中不保留父节点
data.nodeIsParent(setting, node, false); // 将当前节点设置为非父节点
node.open = false; // 关闭当前节点
var tmp_switchObj = $$(node, consts.id.SWITCH, setting), // 获取当前节点的开关对象
tmp_icoObj = $$(node, consts.id.ICON, setting); // 获取当前节点的图标对象
view.replaceSwitchClass(node, tmp_switchObj, consts.folder.DOCU); // 替换开关对象的类为文档文件夹样式
view.replaceIcoClass(node, tmp_icoObj, consts.folder.DOCU); // 替换图标对象的类为文档文件夹样式
$$(node, consts.id.UL, setting).remove(); // 移除当前节点的子节点列表
} else {
$$(node, consts.id.UL, setting).empty(); // 如果保留父节点,则清空子节点列表
}
scrollIntoView: function (setting, dom) {
// 如果dom不存在直接返回
if (!dom) {
return;
}
// 支持IE 7 / 8
if (typeof Element === 'undefined' || typeof HTMLElement === 'undefined') {
// 获取容器的边界矩形
var contRect = setting.treeObj.get(0).getBoundingClientRect(),
// 获取目标元素的边界矩形
findMeRect = dom.getBoundingClientRect();
// 检查目标元素是否在容器视图之外
if (findMeRect.top < contRect.top || findMeRect.bottom > contRect.bottom
|| findMeRect.right > contRect.right || findMeRect.left < contRect.left) {
// 将目标元素滚动到视图中
dom.scrollIntoView();
}
return;
}
// CC-BY jocki84@googlemail.com, https://gist.github.com/jocki84/6ffafd003387179a988e
if (!Element.prototype.scrollIntoViewIfNeeded) {
// 如果浏览器不支持 scrollIntoViewIfNeeded 方法,则定义该方法
Element.prototype.scrollIntoViewIfNeeded = function (centerIfNeeded) {
"use strict";
// 创建一个表示范围的对象,包含起始位置和长度
function makeRange(start, length) {
return {"start": start, "length": length, "end": start + length};
}
// 根据是否需要居中来调整范围的覆盖方式
function coverRange(inner, outer) {
if (
false === centerIfNeeded ||
(outer.start < inner.end && inner.start < outer.end)
) {
// 如果不居中或内外范围有重叠,返回最大值或最小值
return Math.max(
inner.end - outer.length,
Math.min(outer.start, inner.start)
);
}
// 如果需要居中,返回中间值
return (inner.start + inner.end - outer.length) / 2;
}
// 创建一个表示点的对象,包含 x 和 y 坐标以及平移方法
function makePoint(x, y) {
return {
"x": x,
"y": y,
"translate": function translate(dX, dY) {
// 平移点的位置
return makePoint(x + dX, y + dY);
}
};
}
/**
* 计算元素在页面中的绝对位置。
* @param {HTMLElement} elem - 需要计算位置的元素。
* @param {Point} pt - 初始点,用于累加偏移量。
* @returns {Point} - 返回元素的绝对位置。
*/
function absolute(elem, pt) {
// 当元素存在时循环执行
while (elem) {
// 将当前元素的偏移量累加到初始点上
pt = pt.translate(elem.offsetLeft, elem.offsetTop);
// 获取当前元素的父级偏移容器
elem = elem.offsetParent;
}
// 返回计算后的绝对位置
return pt;
}
var target = absolute(this, makePoint(0, 0)), // 计算目标元素的绝对位置
extent = makePoint(this.offsetWidth, this.offsetHeight), // 获取目标元素的宽度和高度
elem = this.parentNode, // 获取目标元素的父节点
origin; // 定义变量origin用于存储当前元素的绝对位置
while (elem instanceof HTMLElement) { // 当elem是HTML元素时循环执行
// Apply desired scroll amount.
origin = absolute(elem, makePoint(elem.clientLeft, elem.clientTop)); // 计算当前元素的绝对位置
elem.scrollLeft = coverRange( // 设置水平滚动条的位置,使其覆盖目标范围
makeRange(target.x - origin.x, extent.x), // 计算目标范围的左边界
makeRange(elem.scrollLeft, elem.clientWidth) // 计算当前可视区域的范围
);
elem.scrollTop = coverRange( // 设置垂直滚动条的位置,使其覆盖目标范围
makeRange(target.y - origin.y, extent.y), // 计算目标范围的上边界
makeRange(elem.scrollTop, elem.clientHeight) // 计算当前可视区域的范围
);
// Determine actual scroll amount by reading back scroll properties.
target = target.translate(-elem.scrollLeft, -elem.scrollTop);
// 将目标元素的位置平移,以考虑滚动条的偏移量
elem = elem.parentNode;
// 将当前元素更新为其父节点以便继续向上遍历DOM树
dom.scrollIntoViewIfNeeded();
// 如果需要将DOM元素滚动到视图中
setFirstNode: function (setting, parentNode) {
// 定义一个方法,用于设置父节点的第一个子节点
var children = data.nodeChildren(setting, parentNode);
// 获取父节点的所有子节点
if (children.length > 0) {
// 如果父节点有子节点
children[0].isFirstNode = true;
// 将第一个子节点标记为第一个节点
}
},
setLastNode: function (setting, parentNode) {
// 获取父节点的所有子节点
var children = data.nodeChildren(setting, parentNode);
// 如果子节点数组不为空则将最后一个子节点标记为isLastNode
if (children.length > 0) {
children[children.length - 1].isLastNode = true;
}
},
removeNode: function (setting, node) {
// 获取根节点
var root = data.getRoot(setting),
// 获取父节点如果当前节点有parentTId属性则获取其父节点否则获取根节点
parentNode = (node.parentTId) ? node.getParentNode() : root;
// 将当前节点的isFirstNode和isLastNode属性设为false
node.isFirstNode = false;
node.isLastNode = false;
// 定义一个方法返回null表示没有前一个节点
node.getPreNode = function () {
return null;
};
// 定义一个方法返回null表示没有下一个节点
node.getNextNode = function () {
return null;
};
// 检查节点缓存是否存在,如果不存在则直接返回
if (!data.getNodeCache(setting, node.tId)) {
return;
}
// 从DOM中移除节点
$$(node, setting).remove();
// 从节点缓存中移除该节点
data.removeNodeCache(setting, node);
// 从选中的节点列表中移除该节点
data.removeSelectedNode(setting, node);
// 获取父节点的所有子节点
var children = data.nodeChildren(setting, parentNode);
// 遍历所有子节点
for (var i = 0, l = children.length; i < l; i++) {
// 如果找到与要删除节点相同的节点
if (children[i].tId == node.tId) {
// 从子节点数组中移除该节点
children.splice(i, 1);
// 退出循环
break;
}
}
view.setFirstNode(setting, parentNode); // 设置第一个节点
view.setLastNode(setting, parentNode); // 设置最后一个节点
var tmp_ulObj, tmp_switchObj, tmp_icoObj,
childLength = children.length; // 获取子节点的长度
// 修复旧父节点的子节点信息
if (!setting.data.keep.parent && childLength == 0) {
// 如果旧父节点没有子节点
data.nodeIsParent(setting, parentNode, false); // 更新数据,标记该节点不是父节点
parentNode.open = false; // 关闭父节点
delete parentNode[setting.data.key.children]; // 删除父节点的子节点属性
tmp_ulObj = $$(parentNode, consts.id.UL, setting); // 获取父节点的UL对象
tmp_switchObj = $$(parentNode, consts.id.SWITCH, setting); // 获取父节点的SWITCH对象
tmp_icoObj = $$(parentNode, consts.id.ICON, setting); // 获取父节点的ICON对象
view.replaceSwitchClass(parentNode, tmp_switchObj, consts.folder.DOCU); // 替换SWITCH对象的类为文档文件夹样式
view.replaceIcoClass(parentNode, tmp_icoObj, consts.folder.DOCU); // 替换ICON对象的类为文档文件夹样式
tmp_ulObj.css("display", "none"); // 隐藏UL对象
}
else if (setting.view.showLine && childLength > 0) {
// 如果设置中显示线条且子节点数量大于0
var newLast = children[childLength - 1];
// 获取最后一个子节点
tmp_ulObj = $$(newLast, consts.id.UL, setting);
// 获取最后一个子节点的UL对象
tmp_switchObj = $$(newLast, consts.id.SWITCH, setting);
// 获取最后一个子节点的SWITCH对象
tmp_icoObj = $$(newLast, consts.id.ICON, setting);
// 获取最后一个子节点的ICON对象
if (parentNode == root) {
// 如果父节点是根节点
if (children.length == 1) {
// 如果只有一个子节点,即该节点原本是根节点
view.replaceSwitchClass(newLast, tmp_switchObj, consts.line.ROOT);
// 替换为根节点样式
} else {
var tmp_first_switchObj = $$(children[0], consts.id.SWITCH, setting);
// 获取第一个子节点的SWITCH对象
view.replaceSwitchClass(children[0], tmp_first_switchObj, consts.line.ROOTS);
// 替换第一个子节点为多根节点样式
view.replaceSwitchClass(newLast, tmp_switchObj, consts.line.BOTTOM);
// 替换最后一个子节点为底部节点样式
}
} else {
view.replaceSwitchClass(newLast, tmp_switchObj, consts.line.BOTTOM);
// 如果不是根节点,则替换最后一个子节点为底部节点样式
}
tmp_ulObj.removeClass(consts.line.LINE);
// 移除UL对象的线条样式
}
},
/**
* 替换节点的图标类名
* @param {Object} node - DOM节点对象
* @param {Object} obj - 包含class属性的对象
* @param {string} newName - 新的类名
*/
replaceIcoClass: function (node, obj, newName) {
// 如果obj为空或节点正在Ajax请求中则直接返回
if (!obj || node.isAjaxing) return;
// 获取对象的class属性值
var tmpName = obj.attr("class");
// 如果class属性值为undefined则直接返回
if (tmpName == undefined) return;
// 将class属性值按"_"分割成数组
var tmpList = tmpName.split("_");
// 根据newName的值进行不同的处理
switch (newName) {
case consts.folder.OPEN:
case consts.folder.CLOSE:
case consts.folder.DOCU:
// 将数组最后一个元素替换为newName
tmpList[tmpList.length - 1] = newName;
break;
}
// 将修改后的数组重新拼接成字符串并设置回对象的class属性
obj.attr("class", tmpList.join("_"));
},
/**
* 替换节点的类名
* @param {Object} node - 需要操作的节点对象
* @param {Object} obj - 包含类名属性的对象
* @param {string} newName - 新的类名
*/
replaceSwitchClass: function (node, obj, newName) {
// 如果obj为空直接返回
if (!obj) return;
// 获取当前对象的class属性值
var tmpName = obj.attr("class");
// 如果class属性值为undefined直接返回
if (tmpName == undefined) return;
// 将class属性值按"_"分割成数组
var tmpList = tmpName.split("_");
// 根据newName的值进行不同的处理
switch (newName) {
case consts.line.ROOT:
case consts.line.ROOTS:
case consts.line.CENTER:
case consts.line.BOTTOM:
case consts.line.NOLINE:
// 更新数组的第一个元素为新的类名
tmpList[0] = view.makeNodeLineClassEx(node) + newName;
break;
case consts.folder.OPEN:
case consts.folder.CLOSE:
case consts.folder.DOCU:
// 更新数组的第二个元素为新的类名
tmpList[1] = newName;
break;
}
// 将更新后的数组重新拼接成字符串并设置回class属性
obj.attr("class", tmpList.join("_"));
// 如果newName不是DOCU移除disabled属性否则添加disabled属性
if (newName !== consts.folder.DOCU) {
obj.removeAttr("disabled");
} else {
obj.attr("disabled", "disabled");
}
},
selectNode: function (setting, node, addFlag) {
// 如果没有添加标志,则取消先前选择的节点
if (!addFlag) {
view.cancelPreSelectedNode(setting, null, node);
}
// 为当前节点添加选中样式
$$(node, consts.id.A, setting).addClass(consts.node.CURSELECTED);
// 将当前节点添加到已选节点列表中
data.addSelectedNode(setting, node);
// 触发选中事件
setting.treeObj.trigger(consts.event.SELECTED, [setting.treeId, node]);
},
setNodeFontCss: function (setting, treeNode) {
// 获取节点对应的DOM对象
var aObj = $$(treeNode, consts.id.A, setting),
// 生成节点字体样式
fontCss = view.makeNodeFontCss(setting, treeNode);
// 如果存在字体样式,则应用到节点上
if (fontCss) {
aObj.css(fontCss);
}
},
/**
* 设置节点的CSS类
* @param {Object} setting - 配置对象
* @param {Object} treeNode - 树节点对象
*/
setNodeClasses: function (setting, treeNode) {
// 获取树节点对应的<a>元素对象
var aObj = $$(treeNode, consts.id.A, setting),
// 生成需要添加和移除的CSS类名集合
classes = view.makeNodeClasses(setting, treeNode);
// 如果需要添加的CSS类名存在且不为空则添加到<a>元素中
if ('add' in classes && classes.add.length) {
aObj.addClass(classes.add.join(' '));
}
// 如果需要移除的CSS类名存在且不为空则从<a>元素中移除
if ('remove' in classes && classes.remove.length) {
aObj.removeClass(classes.remove.join(' '));
}
},
setNodeLineIcos: function (setting, node) {
// 如果节点不存在,直接返回
if (!node) return;
// 获取节点的开关对象、UL对象和图标对象
var switchObj = $$(node, consts.id.SWITCH, setting),
ulObj = $$(node, consts.id.UL, setting),
icoObj = $$(node, consts.id.ICON, setting),
// 生成UL对象的行样式类名
ulLine = view.makeUlLineClass(setting, node);
// 如果行样式类名为空移除UL对象的行样式类名
if (ulLine.length == 0) {
ulObj.removeClass(consts.line.LINE);
} else {
// 否则添加行样式类名到UL对象
ulObj.addClass(ulLine);
}
// 设置开关对象的类名
switchObj.attr("class", view.makeNodeLineClass(setting, node));
// 如果节点是父节点,移除开关对象的禁用属性;否则,设置开关对象为禁用状态
if (data.nodeIsParent(setting, node)) {
switchObj.removeAttr("disabled");
} else {
switchObj.attr("disabled", "disabled");
}
// 移除图标对象的内联样式
icoObj.removeAttr("style");
// 设置图标对象的内联样式和类名
icoObj.attr("style", view.makeNodeIcoStyle(setting, node));
icoObj.attr("class", view.makeNodeIcoClass(setting, node));
},
setNodeName: function (setting, node) {
// 获取节点的标题
var title = data.nodeTitle(setting, node),
// 获取节点对应的SPAN元素对象
nObj = $$(node, consts.id.SPAN, setting);
// 清空SPAN元素的内容
nObj.empty();
// 如果视图名称是HTML格式
if (setting.view.nameIsHTML) {
// 将节点的名称作为HTML内容设置到SPAN元素中
nObj.html(data.nodeName(setting, node));
} else {
// 将节点的名称作为文本内容设置到SPAN元素中
nObj.text(data.nodeName(setting, node));
}
// 如果需要显示标题
if (tools.apply(setting.view.showTitle, [setting.treeId, node], setting.view.showTitle)) {
// 获取节点对应的A元素对象
var aObj = $$(node, consts.id.A, setting);
// 设置A元素的title属性为节点的标题如果标题为空则设置为空字符串
aObj.attr("title", !title ? "" : title);
}
},
setNodeTarget: function (setting, node) {
// 获取节点对应的<a>元素对象
var aObj = $$(node, consts.id.A, setting);
// 设置<a>元素的target属性为生成的节点目标值
aObj.attr("target", view.makeNodeTarget(node));
},
setNodeUrl: function (setting, node) {
// 获取节点对应的<a>元素对象
var aObj = $$(node, consts.id.A, setting),
// 生成节点的URL
url = view.makeNodeUrl(setting, node);
// 如果URL为空或长度为0则移除<a>元素的href属性
if (url == null || url.length == 0) {
aObj.removeAttr("href");
} else {
// 否则,设置<a>元素的href属性为生成的URL
aObj.attr("href", url);
}
},
switchNode: function (setting, node) {
// 如果节点已经打开,或者节点不能异步加载,则切换节点的展开/折叠状态
if (node.open || !tools.canAsync(setting, node)) {
// 调用视图方法切换节点的展开/折叠状态
view.expandCollapseNode(setting, node, !node.open);
} else if (setting.async.enable) {
// 如果启用了异步加载,并且节点不能异步加载,则切换节点的展开/折叠状态
if (!view.asyncNode(setting, node)) {
// 调用视图方法切换节点的展开/折叠状态
view.expandCollapseNode(setting, node, !node.open);
return; // 结束函数执行
}
} else if (node) {
// 如果节点存在,则切换节点的展开/折叠状态
view.expandCollapseNode(setting, node, !node.open);
}
}
};
// zTree defind
$.fn.zTree = {
// 常量对象,包含一些全局的常量值
consts: _consts,
// 内部工具对象,包含各种工具方法
_z: {
tools: tools,
// 视图相关的方法
view: view,
// 事件相关的方法
event: event,
// 数据相关的方法
data: data
},
/**
* 获取指定树ID的zTree对象
* @param {string} treeId - 树的ID
* @returns {Object|null} 返回对应的zTree对象如果不存在则返回null
*/
getZTreeObj: function (treeId) {
// 从data中获取zTree工具对象
var o = data.getZTreeTools(treeId);
// 如果存在则返回该对象否则返回null
return o ? o : null;
},
/**
* 销毁指定的zTree实例或所有zTree实例
* @param {string} [treeId] - 可选参数树的ID。如果提供则销毁指定ID的树否则销毁所有树
*/
destroy: function (treeId) {
// 如果提供了treeId且其长度大于0
if (!!treeId && treeId.length > 0) {
// 销毁指定ID的树
view.destroy(data.getSetting(treeId));
} else {
// 遍历所有设置并销毁每一个树
for (var s in settings) {
view.destroy(settings[s]);
}
}
},
init: function (obj, zSetting, zNodes) {
// 克隆默认设置对象
var setting = tools.clone(_setting);
// 深度合并用户自定义设置到默认设置中
$.extend(true, setting, zSetting);
// 设置树的ID为传入对象的ID
setting.treeId = obj.attr("id");
// 将传入的对象赋值给树对象
setting.treeObj = obj;
// 清空树对象的内容
setting.treeObj.empty();
// 将设置存储在全局settings对象中以树ID为键
settings[setting.treeId] = setting;
// 针对一些旧浏览器例如ie6如果不支持maxHeight样式属性则禁用展开速度
if (typeof document.body.style.maxHeight === "undefined") {
setting.view.expandSpeed = "";
}
// 初始化根节点数据
data.initRoot(setting);
// 获取根节点数据
var root = data.getRoot(setting);
// 如果zNodes存在则克隆并转换为数组格式否则设置为空数组
zNodes = zNodes ? tools.clone(tools.isArray(zNodes) ? zNodes : [zNodes]) : [];
// 如果启用了简单数据模式,则转换节点数据格式后添加到根节点
if (setting.data.simpleData.enable) {
data.nodeChildren(setting, root, data.transformTozTreeFormat(setting, zNodes));
} else {
// 否则直接添加节点数据到根节点
data.nodeChildren(setting, root, zNodes);
}
// 初始化缓存,传入设置参数
data.initCache(setting);
// 解绑树形结构的事件,传入设置参数
event.unbindTree(setting);
// 绑定树形结构的事件,传入设置参数
event.bindTree(setting);
// 解绑其他事件,传入设置参数
event.unbindEvent(setting);
// 绑定其他事件,传入设置参数
event.bindEvent(setting);
var zTreeTools = {
// 设置对象
setting: setting,
/**
* 添加节点函数
* @param {Object} parentNode - 父节点对象
* @param {number|string} index - 插入位置的索引或新节点数组
* @param {Array|Object} newNodes - 要添加的新节点,可以是单个节点对象或节点数组
* @param {boolean} isSilent - 是否静默添加(不触发事件)
* @returns {null|undefined} 如果无法添加节点则返回null
*/
addNodes: function (parentNode, index, newNodes, isSilent) {
// 如果未提供父节点则默认为null
if (!parentNode) parentNode = null;
// 检查父节点是否是父节点类型
var isParent = data.nodeIsParent(setting, parentNode);
// 如果父节点不是父节点且配置中要求保持叶子节点则返回null
if (parentNode && !isParent && setting.data.keep.leaf) return null;
// 将index转换为整数
var i = parseInt(index, 10);
// 如果index不是数字则调整参数
if (isNaN(i)) {
isSilent = !!newNodes; // 判断newNodes是否存在存在则isSilent为true
newNodes = index; // 将index赋值给newNodes
index = -1; // 重置index为-1
} else {
index = i; // 否则使用转换后的整数index
}
// 如果newNodes不存在则返回null
if (!newNodes) return null;
// 克隆新的节点数组,确保其为数组形式
var xNewNodes = tools.clone(tools.isArray(newNodes) ? newNodes : [newNodes]);
// 定义添加节点的回调函数
function addCallback() {
// 调用视图层的addNodes方法实际添加节点
view.addNodes(setting, parentNode, index, xNewNodes, (isSilent == true));
}
if (tools.canAsync(setting, parentNode)) {
// 检查是否可以异步加载节点如果可以则调用view.asyncNode方法进行异步加载
view.asyncNode(setting, parentNode, isSilent, addCallback);
} else {
// 如果不支持异步加载,直接调用回调函数
addCallback();
}
// 返回新添加的节点
return xNewNodes;
},
cancelSelectedNode: function (node) {
// 取消选中的节点
view.cancelPreSelectedNode(setting, node);
},
destroy: function () {
// 销毁视图
view.destroy(setting);
},
expandAll: function (expandFlag) {
// 确保expandFlag为布尔值
expandFlag = !!expandFlag;
// 展开或折叠所有子节点
view.expandCollapseSonNode(setting, null, expandFlag, true);
// 返回最终的展开标志
return expandFlag;
},
expandNode: function (node, expandFlag, sonSign, focus, callbackFlag) {
// 如果节点不存在或者不是父节点返回null
if (!node || !data.nodeIsParent(setting, node)) return null;
// 如果expandFlag既不是true也不是false则取反当前节点的open状态
if (expandFlag !== true && expandFlag !== false) {
expandFlag = !node.open;
}
// 确保callbackFlag为布尔值
callbackFlag = !!callbackFlag;
// 检查是否需要在展开或折叠节点之前执行回调函数
if (callbackFlag && expandFlag && (tools.apply(setting.callback.beforeExpand, [setting.treeId, node], true) == false)) {
// 如果回调函数返回false则不进行展开操作直接返回null
return null;
} else if (callbackFlag && !expandFlag && (tools.apply(setting.callback.beforeCollapse, [setting.treeId, node], true) == false)) {
// 如果回调函数返回false则不进行折叠操作直接返回null
return null;
}
// 如果需要展开节点且该节点有父节点
if (expandFlag && node.parentTId) {
// 递归展开父节点
view.expandCollapseParentNode(setting, node.getParentNode(), expandFlag, false);
}
// 如果节点的展开状态与目标状态相同且没有子节点标志
if (expandFlag === node.open && !sonSign) {
// 不需要改变节点状态直接返回null
return null;
}
// 设置根节点的展开触发标志为回调标志
data.getRoot(setting).expandTriggerFlag = callbackFlag;
// 如果当前节点不能异步加载并且有子节点
if (!tools.canAsync(setting, node) && sonSign) {
// 展开或折叠子节点,并显示节点焦点
view.expandCollapseSonNode(setting, node, expandFlag, true, showNodeFocus);
} else {
// 否则,根据展开标志设置节点的打开状态
node.open = !expandFlag;
// 切换节点的状态
view.switchNode(this.setting, node);
// 显示节点焦点
showNodeFocus();
}
// 返回展开标志
return expandFlag;
function showNodeFocus() {
// 获取节点对象
var a = $$(node, setting).get(0);
// 如果节点存在且焦点不为false则将视图滚动到该节点位置
if (a && focus !== false) {
view.scrollIntoView(setting, a);
}
}
},
getNodes: function () {
// 返回所有节点数据
return data.getNodes(setting);
},
getNodeByParam: function (key, value, parentNode) {
// 如果键不存在返回null
if (!key) return null;
// 根据参数获取节点如果parentNode存在则在子节点中查找否则在所有节点中查找
return data.getNodeByParam(setting, parentNode ? data.nodeChildren(setting, parentNode) : data.getNodes(setting), key, value);
},
getNodeByTId: function (tId) {
// 根据节点ID获取缓存中的节点对象
return data.getNodeCache(setting, tId);
},
getNodesByParam: function (key, value, parentNode) {
// 如果键不存在返回null
if (!key) return null;
// 根据参数获取节点数组如果parentNode存在则在子节点中查找否则在所有节点中查找
return data.getNodesByParam(setting, parentNode ? data.nodeChildren(setting, parentNode) : data.getNodes(setting), key, value);
},
getNodesByParamFuzzy: function (key, value, parentNode) {
// 如果键不存在返回null
if (!key) return null;
// 根据模糊参数获取节点数组如果parentNode存在则在子节点中查找否则在所有节点中查找
return data.getNodesByParamFuzzy(setting, parentNode ? data.nodeChildren(setting, parentNode) : data.getNodes(setting), key, value);
},
getNodesByFilter: function (filter, isSingle, parentNode, invokeParam) {
// 将isSingle转换为布尔值
isSingle = !!isSingle;
// 如果过滤器不存在或不是函数返回null或空数组
if (!filter || (typeof filter != "function")) return (isSingle ? null : []);
// 根据过滤器获取节点数组如果parentNode存在则在子节点中查找否则在所有节点中查找
return data.getNodesByFilter(setting, parentNode ? data.nodeChildren(setting, parentNode) : data.getNodes(setting), filter, isSingle, invokeParam);
},
getNodeIndex: function (node) {
// 如果节点为空返回null
if (!node) return null;
// 获取父节点如果节点有parentTId属性则调用getParentNode方法否则获取根节点
var parentNode = (node.parentTId) ? node.getParentNode() : data.getRoot(setting);
// 获取父节点的子节点列表
var children = data.nodeChildren(setting, parentNode);
// 遍历子节点列表,找到与目标节点匹配的索引并返回
for (var i = 0, l = children.length; i < l; i++) {
if (children[i] == node) return i;
}
// 如果未找到匹配的节点,返回-1
return -1;
},
getSelectedNodes: function () {
// 初始化一个空数组用于存储选中的节点
var r = [], list = data.getRoot(setting).curSelectedList;
// 遍历当前选中的节点列表,将每个节点添加到结果数组中
for (var i = 0, l = list.length; i < l; i++) {
r.push(list[i]);
}
// 返回包含所有选中节点的数组
return r;
},
isSelectedNode: function (node) {
// 判断指定节点是否被选中,返回布尔值
return data.isSelectedNode(setting, node);
},
reAsyncChildNodesPromise: function (parentNode, reloadType, isSilent) {
// 创建一个新的Promise对象
var promise = new Promise(function (resolve, reject) {
try {
// 异步重新加载子节点并在完成后解析Promise
zTreeTools.reAsyncChildNodes(parentNode, reloadType, isSilent, function () {
resolve(parentNode);
});
} catch (e) {
// 如果发生错误拒绝Promise并传递错误信息
reject(e);
}
});
// 返回Promise对象
return promise;
},
/**
* 重新加载异步子节点
* @param {Object} parentNode - 父节点对象
* @param {string} reloadType - 重载类型,可以是 "refresh"
* @param {boolean} isSilent - 是否静默加载
* @param {function} callback - 回调函数
*/
reAsyncChildNodes: function (parentNode, reloadType, isSilent, callback) {
// 如果异步加载未启用,则直接返回
if (!this.setting.async.enable) return;
// 判断是否是根节点
var isRoot = !parentNode;
// 如果是根节点,获取根节点数据
if (isRoot) {
parentNode = data.getRoot(setting);
}
// 如果重载类型是 "refresh"
if (reloadType == "refresh") {
// 获取当前父节点的所有子节点
var children = data.nodeChildren(setting, parentNode);
// 遍历所有子节点并移除缓存
for (var i = 0, l = children ? children.length : 0; i < l; i++) {
data.removeNodeCache(setting, children[i]);
}
// 移除选中的节点
data.removeSelectedNode(setting);
// 清空父节点的子节点列表
data.nodeChildren(setting, parentNode, []);
// 如果是根节点,清空树对象
if (isRoot) {
this.setting.treeObj.empty();
} else {
// 否则清空父节点对应的UL元素
var ulObj = $$(parentNode, consts.id.UL, setting);
ulObj.empty();
}
}
// 异步加载节点
view.asyncNode(this.setting, isRoot ? null : parentNode, !!isSilent, callback);
},
refresh: function () {
// 清空树对象
this.setting.treeObj.empty();
// 获取根节点
var root = data.getRoot(setting),
// 获取根节点的子节点
nodes = data.nodeChildren(setting, root);
// 初始化根节点
data.initRoot(setting);
// 将子节点重新添加到根节点
data.nodeChildren(setting, root, nodes);
// 初始化缓存
data.initCache(setting);
// 创建节点视图参数依次为设置、层级、子节点数组、父节点、父节点ID
view.createNodes(setting, 0, data.nodeChildren(setting, root), null, -1);
},
removeChildNodes: function (node) {
// 如果节点为空返回null
if (!node) return null;
// 获取指定节点的子节点
var nodes = data.nodeChildren(setting, node);
// 从视图中移除这些子节点
view.removeChildNodes(setting, node);
// 如果有子节点则返回它们否则返回null
return nodes ? nodes : null;
},
removeNode: function (node, callbackFlag) {
// 如果节点为空,直接返回
if (!node) return;
// 确保callbackFlag是布尔值
callbackFlag = !!callbackFlag;
// 如果callbackFlag为true且beforeRemove回调函数返回false则不继续执行
if (callbackFlag && tools.apply(setting.callback.beforeRemove, [setting.treeId, node], true) == false) return;
// 从视图中移除该节点
view.removeNode(setting, node);
// 如果callbackFlag为true触发REMOVE事件
if (callbackFlag) {
this.setting.treeObj.trigger(consts.event.REMOVE, [setting.treeId, node]);
}
},
selectNode: function (node, addFlag, isSilent) {
// 如果节点为空,直接返回
if (!node) return;
// 检查用户是否有权限进行操作
if (tools.uCanDo(setting)) {
// 如果允许多选并且addFlag为true则保持addFlag为true
addFlag = setting.view.selectedMulti && addFlag;
// 如果节点有父节点
if (node.parentTId) {
// 展开父节点并聚焦到该节点
view.expandCollapseParentNode(setting, node.getParentNode(), true, false, showNodeFocus);
} else if (!isSilent) {
// 如果节点没有父节点且不静默处理
try {
// 尝试让节点获取焦点并失去焦点
$$(node, setting).focus().blur();
} catch (e) {
// 捕获异常但不做任何处理
}
}
// 选择节点根据addFlag决定是否添加选中状态
view.selectNode(setting, node, addFlag);
}
function showNodeFocus() {
// 如果当前处于静默模式,则直接返回,不执行后续操作
if (isSilent) {
return;
}
// 获取指定节点的第一个元素
var a = $$(node, setting).get(0);
// 将视图滚动到指定节点的位置
view.scrollIntoView(setting, a);
}
/**
* 将简单节点数组转换为zTree节点格式
* @param {Array} simpleNodes - 简单节点数组
* @returns {Array} zTree节点格式的数组
*/
transformTozTreeNodes: function (simpleNodes) {
return data.transformTozTreeFormat(setting, simpleNodes);
},
/**
* 将节点数组转换为普通数组格式
* @param {Array} nodes - 节点数组
* @returns {Array} 普通数组格式的数组
*/
transformToArray: function (nodes) {
return data.transformToArrayFormat(setting, nodes);
},
updateNode: function (node, checkTypeFlag) {
// 如果节点不存在,直接返回
if (!node) return;
// 获取节点对象
var nObj = $$(node, setting);
// 如果节点对象存在且用户有权限进行操作
if (nObj.get(0) && tools.uCanDo(setting)) {
// 设置节点名称
view.setNodeName(setting, node);
// 设置节点目标
view.setNodeTarget(setting, node);
// 设置节点URL
view.setNodeUrl(setting, node);
// 设置节点图标
view.setNodeLineIcos(setting, node);
// 设置节点字体样式
view.setNodeFontCss(setting, node);
// 设置节点类名
view.setNodeClasses(setting, node);
}
}
root.treeTools = zTreeTools; // 将zTreeTools对象赋值给根节点的treeTools属性
data.setZTreeTools(setting, zTreeTools); // 设置zTree工具到数据对象中
var children = data.nodeChildren(setting, root); // 获取根节点的子节点
if (children && children.length > 0) { // 如果存在子节点且子节点数量大于0
view.createNodes(setting, 0, children, null, -1); // 创建子节点视图
} else if (setting.async.enable && setting.async.url && setting.async.url !== '') { // 如果启用了异步加载并且URL不为空
view.asyncNode(setting); // 异步加载节点
}
return zTreeTools; // 返回zTree工具对象
var zt = $.fn.zTree, // 获取jQuery的zTree插件
$$ = tools.$, // 从tools对象中获取$符号,通常用于简化代码书写
consts = zt.consts; // 获取zTree插件中的常量定义
})(jQuery); // 立即执行函数传入jQuery对象作为参数
/*
* JQuery zTree excheck
* v3.5.46
* http://treejs.cn/
*
* Copyright (c) 2010 Hunter.z
*
* Licensed same as jquery - MIT License
* http://www.opensource.org/licenses/mit-license.php
*
* Date: 2020-11-21
*/
(function ($) {
// 定义excheck的默认常量
var _consts = {
event: {
// 触发检查事件的名称
CHECK: "ztree_check"
},
id: {
// 检查框的ID前缀
CHECK: "_check"
},
checkbox: {
// 复选框样式类型
STYLE: "checkbox",
// 默认复选框类名
DEFAULT: "chk",
// 禁用状态的复选框类名
DISABLED: "disable",
// 未选中状态的复选框类名
FALSE: "false",
// 选中状态的复选框类名
TRUE: "true",
// 完全选中状态的复选框类名
FULL: "full",
// 部分选中状态的复选框类名
PART: "part",
// 焦点状态的复选框类名
FOCUS: "focus"
},
radio: {
// 单选按钮样式类型
STYLE: "radio",
// 所有节点都参与选择的类型
TYPE_ALL: "all",
// 同级别节点参与选择的类型
TYPE_LEVEL: "level"
}
},
//default setting of excheck
_setting = {
// 配置检查选项
check: {
// 是否启用检查功能默认为false
enable: false,
// 是否自动触发检查默认为false
autoCheckTrigger: false,
// 设置复选框的样式,使用常量中的样式定义
chkStyle: _consts.checkbox.STYLE,
// 是否不继承未选中状态默认为false
nocheckInherit: false,
// 是否不继承禁用状态默认为false
chkDisabledInherit: false,
// 设置单选按钮的类型,使用常量中定义的类型
radioType: _consts.radio.TYPE_LEVEL,
// 设置复选框类型,"Y"和"N"都对应到"ps"
chkboxType: {
"Y": "ps",
"N": "ps"
}
},
data: {
key: {
checked: "checked" // 定义节点是否被选中的键名
}
},
callback: {
beforeCheck: null, // 在节点选中前执行的回调函数
onCheck: null // 在节点选中后执行的回调函数
}
},
// default root of excheck
_initRoot = function (setting) {
var r = data.getRoot(setting); // 获取树的根节点
r.radioCheckedList = []; // 初始化根节点的单选列表为空数组
},
// default cache of excheck
_initCache = function (treeId) {
// 初始化缓存,当前未实现具体逻辑
},
// default bind event of excheck
_bindEvent = function (setting) {
var o = setting.treeObj, // 获取树对象
c = consts.event; // 获取事件常量
o.bind(c.CHECK, function (event, srcEvent, treeId, node) {
event.srcEvent = srcEvent; // 将源事件附加到事件对象上
tools.apply(setting.callback.onCheck, [event, treeId, node]); // 调用选中后的回调函数
});
},
_unbindEvent = function (setting) {
// 获取树对象
var o = setting.treeObj,
// 获取事件常量
c = consts.event;
// 解绑CHECK事件
o.unbind(c.CHECK);
},
//default event proxy of excheck
_eventProxy = function (e) {
// 获取事件目标元素
var target = e.target,
// 获取设置信息
setting = data.getSetting(e.data.treeId),
tId = "", node = null,
nodeEventType = "", treeEventType = "",
nodeEventCallback = null, treeEventCallback = null;
// 如果事件类型是mouseover
if (tools.eqs(e.type, "mouseover")) {
// 如果启用了复选框并且目标元素是span标签且具有CHECK属性
if (setting.check.enable && tools.eqs(target.tagName, "span") && target.getAttribute("treeNode" + consts.id.CHECK) !== null) {
// 获取节点主DOM的ID
tId = tools.getNodeMainDom(target).id;
// 设置节点事件类型为mouseoverCheck
nodeEventType = "mouseoverCheck";
}
} else if (tools.eqs(e.type, "mouseout")) {
// 如果事件类型是mouseout
// 如果启用了复选框并且目标元素是span标签且具有CHECK属性
if (setting.check.enable && tools.eqs(target.tagName, "span") && target.getAttribute("treeNode" + consts.id.CHECK) !== null) {
// 获取节点主DOM的ID
tId = tools.getNodeMainDom(target).id;
// 设置节点事件类型为mouseoutCheck
nodeEventType = "mouseoutCheck";
}
} else if (tools.eqs(e.type, "click")) {
// 如果事件类型是click
// 如果启用了复选框并且目标元素是span标签且具有CHECK属性
if (setting.check.enable && tools.eqs(target.tagName, "span") && target.getAttribute("treeNode" + consts.id.CHECK) !== null) {
// 获取节点主DOM的ID
tId = tools.getNodeMainDom(target).id;
// 设置节点事件类型为checkNode
nodeEventType = "checkNode";
}
}
// 检查tId数组的长度是否大于0
if (tId.length > 0) {
// 从缓存中获取节点对象
node = data.getNodeCache(setting, tId);
// 根据节点事件类型选择相应的回调函数
switch (nodeEventType) {
// 如果事件类型是"checkNode"则使用_handler.onCheckNode作为回调函数
case "checkNode":
nodeEventCallback = _handler.onCheckNode;
break;
// 如果事件类型是"mouseoverCheck"则使用_handler.onMouseoverCheck作为回调函数
case "mouseoverCheck":
nodeEventCallback = _handler.onMouseoverCheck;
break;
// 如果事件类型是"mouseoutCheck"则使用_handler.onMouseoutCheck作为回调函数
case "mouseoutCheck":
nodeEventCallback = _handler.onMouseoutCheck;
break;
}
}
var proxyResult = {
// 如果nodeEventType是"checkNode"则stop为true否则为false
stop: nodeEventType === "checkNode",
// 当前节点对象
node: node,
// 节点事件类型
nodeEventType: nodeEventType,
// 节点事件回调函数
nodeEventCallback: nodeEventCallback,
// 树事件类型
treeEventType: treeEventType,
// 树事件回调函数
treeEventCallback: treeEventCallback
};
// 返回包含上述属性的对象
return proxyResult
},
//default init node of excheck
_initNode = function (setting, level, n, parentNode, isFirstNode, isLastNode, openFlag) {
// 如果节点不存在,直接返回
if (!n) return;
// 获取节点的选中状态
var checked = data.nodeChecked(setting, n);
// 保存节点的旧选中状态
n.checkedOld = checked;
// 如果节点的nocheck属性是字符串类型将其转换为布尔值
if (typeof n.nocheck == "string") n.nocheck = tools.eqs(n.nocheck, "true");
// 设置节点的nocheck属性如果父节点存在且继承父节点的nocheck属性则使用父节点的值
n.nocheck = !!n.nocheck || (setting.check.nocheckInherit && parentNode && !!parentNode.nocheck);
// 如果节点的chkDisabled属性是字符串类型将其转换为布尔值
if (typeof n.chkDisabled == "string") n.chkDisabled = tools.eqs(n.chkDisabled, "true");
// 设置节点的chkDisabled属性如果父节点存在且继承父节点的chkDisabled属性则使用父节点的值
n.chkDisabled = !!n.chkDisabled || (setting.check.chkDisabledInherit && parentNode && !!parentNode.chkDisabled);
// 如果节点的halfCheck属性是字符串类型将其转换为布尔值
if (typeof n.halfCheck == "string") n.halfCheck = tools.eqs(n.halfCheck, "true");
// 设置节点的halfCheck属性为布尔值
n.halfCheck = !!n.halfCheck;
// 初始化子节点的状态为-1未定义
n.check_Child_State = -1;
// 初始化节点的焦点状态为false
n.check_Focus = false;
// 定义一个方法来获取节点的选中状态
n.getCheckStatus = function () {
return data.getCheckStatus(setting, n);
};
}
if (setting.check.chkStyle == consts.radio.STYLE && setting.check.radioType == consts.radio.TYPE_ALL && checked) {
// 获取根节点数据
var r = data.getRoot(setting);
// 将当前节点添加到已选中的单选按钮列表中
r.radioCheckedList.push(n);
}
//add dom for check
_beforeA = function (setting, node, html) {
// 检查是否启用了check功能
if (setting.check.enable) {
// 调用makeChkFlag方法为节点生成check标记
data.makeChkFlag(setting, node);
// 将生成的HTML片段推入html数组中
html.push("<span ID='", node.tId, consts.id.CHECK, "' class='", view.makeChkClass(setting, node), "' treeNode", consts.id.CHECK, (node.nocheck === true ? " style='display:none;'" : ""), "></span>");
}
},
//update zTreeObj, add method of check
_zTreeTools = function (setting, zTreeTools) {
// 定义一个方法,用于检查节点的状态
zTreeTools.checkNode = function (node, checked, checkTypeFlag, callbackFlag) {
// 获取当前节点的选中状态
var nodeChecked = data.nodeChecked(setting, node);
// 如果节点被禁用,则直接返回
if (node.chkDisabled === true) return;
// 如果checked参数不是布尔值则取反当前节点的选中状态
if (checked !== true && checked !== false) {
checked = !nodeChecked;
}
// 确保callbackFlag为布尔值
callbackFlag = !!callbackFlag;
// 如果节点的选中状态没有变化且不需要回调,则直接返回
if (nodeChecked === checked && !checkTypeFlag) {
return;
// 如果需要回调且beforeCheck回调函数返回false则直接返回
} else if (callbackFlag && tools.apply(this.setting.callback.beforeCheck, [this.setting.treeId, node], true) == false) {
return;
}
// 检查用户是否有权限进行操作,并且是否启用了节点选中功能,以及节点是否没有被禁用选中
if (tools.uCanDo(this.setting) && this.setting.check.enable && node.nocheck !== true) {
// 更新节点的选中状态
data.nodeChecked(setting, node, checked);
// 获取节点对应的复选框对象
var checkObj = $$(node, consts.id.CHECK, this.setting);
// 如果需要检查类型标志或节点样式为单选,则更新节点关系
if (checkTypeFlag || this.setting.check.chkStyle === consts.radio.STYLE) view.checkNodeRelation(this.setting, node);
// 设置复选框的样式
view.setChkClass(this.setting, checkObj, node);
// 修复父节点的复选框样式
view.repairParentChkClassWithSelf(this.setting, node);
// 如果需要回调则触发CHECK事件
if (callbackFlag) {
this.setting.treeObj.trigger(consts.event.CHECK, [null, this.setting.treeId, node]);
}
}
}
zTreeTools.checkAllNodes = function (checked) {
// 调用view对象的repairAllChk方法修复所有节点的选中状态
view.repairAllChk(this.setting, !!checked);
}
zTreeTools.getCheckedNodes = function (checked) {
// 如果checked参数不为false则将其转换为布尔值true
checked = (checked !== false);
// 获取根节点的所有子节点
var children = data.nodeChildren(setting, data.getRoot(this.setting));
// 返回树中所有被选中的节点
return data.getTreeCheckedNodes(this.setting, children, checked);
}
zTreeTools.getChangeCheckedNodes = function () {
// 获取根节点的所有子节点
var children = data.nodeChildren(setting, data.getRoot(this.setting));
// 返回树中所有改变选中状态的节点
return data.getTreeChangeCheckedNodes(this.setting, children);
}
zTreeTools.setChkDisabled = function (node, disabled, inheritParent, inheritChildren) {
// 将disabled参数转换为布尔值
disabled = !!disabled;
// 将inheritParent参数转换为布尔值
inheritParent = !!inheritParent;
// 将inheritChildren参数转换为布尔值
inheritChildren = !!inheritChildren;
// 修复子节点的禁用状态
view.repairSonChkDisabled(this.setting, node, disabled, inheritChildren);
// 修复父节点的禁用状态
view.repairParentChkDisabled(this.setting, node.getParentNode(), disabled, inheritParent);
}
var _updateNode = zTreeTools.updateNode;
zTreeTools.updateNode = function (node, checkTypeFlag) {
// 如果存在原始的updateNode方法则调用它
if (_updateNode) _updateNode.apply(zTreeTools, arguments);
// 如果节点不存在或检查功能未启用,则直接返回
if (!node || !this.setting.check.enable) return;
// 获取节点对应的jQuery对象
var nObj = $$(node, this.setting);
// 如果节点存在且用户有权限操作
if (nObj.get(0) && tools.uCanDo(this.setting)) {
// 获取节点的复选框对象
var checkObj = $$(node, consts.id.CHECK, this.setting);
// 如果checkTypeFlag为true或复选框样式为单选按钮
if (checkTypeFlag == true || this.setting.check.chkStyle === consts.radio.STYLE)
// 修复节点的选中关系
view.checkNodeRelation(this.setting, node);
// 设置复选框的CSS类
view.setChkClass(this.setting, checkObj, node);
// 修复父节点的CSS类
view.repairParentChkClassWithSelf(this.setting, node);
}
}
},
//method of operate data
_data = {
// 获取选中的单选框列表
getRadioCheckedList: function (setting) {
// 获取根节点的单选框选中列表
var checkedList = data.getRoot(setting).radioCheckedList;
// 遍历选中列表,检查每个节点是否在缓存中存在
for (var i = 0, j = checkedList.length; i < j; i++) {
// 如果节点不在缓存中,则从选中列表中移除
if (!data.getNodeCache(setting, checkedList[i].tId)) {
checkedList.splice(i, 1);
i--;
j--;
}
}
// 返回处理后的选中列表
return checkedList;
},
// 获取节点的选中状态
getCheckStatus: function (setting, node) {
// 如果未启用选中功能或节点不可选中则返回null
if (!setting.check.enable || node.nocheck || node.chkDisabled) return null;
// 获取节点是否被选中的状态
var checked = data.nodeChecked(setting, node),
r = {
checked: checked,
// 根据不同的选中样式和子节点状态计算半选状态
half: node.halfCheck ? node.halfCheck : (setting.check.chkStyle == consts.radio.STYLE ? (node.check_Child_State === 2) : (checked ? (node.check_Child_State > -1 && node.check_Child_State < 2) : (node.check_Child_State > 0)))
};
// 返回选中状态对象
return r;
},
// 获取树形结构中所有选中的节点
getTreeCheckedNodes: function (setting, nodes, checked, results) {
// 如果节点列表为空,则返回空数组
if (!nodes) return [];
// 判断是否只允许选择一个节点(单选模式)
var onlyOne = (checked && setting.check.chkStyle == consts.radio.STYLE && setting.check.radioType == consts.radio.TYPE_ALL);
// 初始化结果数组
results = !results ? [] : results;
// 遍历节点列表
for (var i = 0, l = nodes.length; i < l; i++) {
var node = nodes[i];
// 获取当前节点的子节点
var children = data.nodeChildren(setting, node);
// 获取当前节点是否被选中
var nodeChecked = data.nodeChecked(setting, node);
// 如果节点可选中且未禁用,并且其选中状态与目标状态一致,则将其加入结果数组
if (node.nocheck !== true && node.chkDisabled !== true && nodeChecked == checked) {
results.push(node);
// 如果只允许选择一个节点,则找到后立即退出循环
if (onlyOne) {
break;
}
}
// 递归处理子节点
data.getTreeCheckedNodes(setting, children, checked, results);
// 如果只允许选择一个节点,且已找到符合条件的节点,则退出循环
if (onlyOne && results.length > 0) {
break;
}
}
// 返回结果数组
return results;
},
getTreeChangeCheckedNodes: function (setting, nodes, results) {
// 如果节点为空,返回空数组
if (!nodes) return [];
// 如果结果未定义,初始化为空数组
results = !results ? [] : results;
// 遍历所有节点
for (var i = 0, l = nodes.length; i < l; i++) {
// 获取当前节点
var node = nodes[i];
// 获取当前节点的子节点
var children = data.nodeChildren(setting, node);
// 检查当前节点是否被选中
var nodeChecked = data.nodeChecked(setting, node);
// 如果节点没有被禁用且选中状态发生变化,则将节点加入结果数组
if (node.nocheck !== true && node.chkDisabled !== true && nodeChecked != node.checkedOld) {
results.push(node);
}
// 递归处理子节点
data.getTreeChangeCheckedNodes(setting, children, results);
}
// 返回结果数组
return results;
},
/**
* 生成节点的检查标志。
* @param {Object} setting - 配置对象,包含树形结构的相关设置。
* @param {Object} node - 当前处理的节点对象。
*/
makeChkFlag: function (setting, node) {
// 如果节点不存在,直接返回
if (!node) return;
// 初始化检查标志为-1
var chkFlag = -1;
// 获取节点的子节点列表
var children = data.nodeChildren(setting, node);
// 如果存在子节点
if (children) {
// 遍历所有子节点
for (var i = 0, l = children.length; i < l; i++) {
var cNode = children[i]; // 当前子节点
var nodeChecked = data.nodeChecked(setting, cNode); // 判断子节点是否被选中
var tmp = -1; // 临时变量,用于存储子节点的状态
// 如果检查样式是单选框
if (setting.check.chkStyle == consts.radio.STYLE) {
// 如果子节点不可检查或禁用,则使用其子状态
if (cNode.nocheck === true || cNode.chkDisabled === true) {
tmp = cNode.check_Child_State;
} else if (cNode.halfCheck === true) {
// 如果子节点半选则状态为2
tmp = 2;
} else if (nodeChecked) {
// 如果子节点被选中则状态为2
tmp = 2;
} else {
// 根据子节点的子状态决定状态
tmp = cNode.check_Child_State > 0 ? 2 : 0;
}
// 如果状态为2更新检查标志并退出循环
if (tmp == 2) {
chkFlag = 2;
break;
} else if (tmp == 0) {
// 如果状态为0更新检查标志为0
chkFlag = 0;
}
} else if (setting.check.chkStyle == consts.checkbox.STYLE) {
// 如果检查样式是复选框
if (cNode.nocheck === true || cNode.chkDisabled === true) {
tmp = cNode.check_Child_State;
} else if (cNode.halfCheck === true) {
// 如果子节点半选则状态为1
tmp = 1;
} else if (nodeChecked) {
// 根据子节点的子状态决定状态
tmp = (cNode.check_Child_State === -1 || cNode.check_Child_State === 2) ? 2 : 1;
} else {
// 根据子节点的子状态决定状态
tmp = (cNode.check_Child_State > 0) ? 1 : 0;
}
// 如果状态为1更新检查标志并退出循环
if (tmp === 1) {
chkFlag = 1;
break;
} else if (tmp === 2 && chkFlag > -1 && i > 0 && tmp !== chkFlag) {
// 如果状态为2且之前有其他状态更新检查标志为1并退出循环
chkFlag = 1;
break;
} else if (chkFlag === 2 && tmp > -1 && tmp < 2) {
// 如果之前状态为2且当前状态在0和2之间更新检查标志为1并退出循环
chkFlag = 1;
break;
} else if (tmp > -1) {
// 更新检查标志为当前状态
chkFlag = tmp;
}
}
}
}
// 将计算得到的检查标志赋值给节点的check_Child_State属性
node.check_Child_State = chkFlag;
}
//method of event proxy
_event = {},
// 事件处理程序的方法集合
_handler = {
/**
* 处理节点选中事件的函数
* @param {Object} event - 事件对象
* @param {Object} node - 被操作的节点对象
* @returns {boolean} - 返回是否成功处理事件
*/
onCheckNode: function (event, node) {
// 如果节点被禁用则直接返回false
if (node.chkDisabled === true) return false;
// 获取当前树的设置信息
var setting = data.getSetting(event.data.treeId);
// 调用beforeCheck回调函数如果返回false则终止操作
if (tools.apply(setting.callback.beforeCheck, [setting.treeId, node], true) == false) return true;
// 获取节点当前的选中状态
var nodeChecked = data.nodeChecked(setting, node);
// 切换节点的选中状态
data.nodeChecked(setting, node, !nodeChecked);
// 更新节点关系
view.checkNodeRelation(setting, node);
// 获取节点对应的复选框元素
var checkObj = $$(node, consts.id.CHECK, setting);
// 设置复选框的样式
view.setChkClass(setting, checkObj, node);
// 修复父节点的复选框样式
view.repairParentChkClassWithSelf(setting, node);
// 触发CHECK事件
setting.treeObj.trigger(consts.event.CHECK, [event, setting.treeId, node]);
return true;
},
/**
* 处理鼠标悬停在复选框上的事件
* @param {Object} event - 事件对象
* @param {Object} node - 被操作的节点对象
* @returns {boolean} - 返回是否成功处理事件
*/
onMouseoverCheck: function (event, node) {
// 如果节点被禁用则直接返回false
if (node.chkDisabled === true) return false;
// 获取当前树的设置信息和复选框元素
var setting = data.getSetting(event.data.treeId),
checkObj = $$(node, consts.id.CHECK, setting);
// 设置节点的check_Focus属性为true
node.check_Focus = true;
// 设置复选框的样式
view.setChkClass(setting, checkObj, node);
return true;
},
/**
* 处理鼠标移出复选框的事件
* @param {Object} event - 事件对象
* @param {Object} node - 被操作的节点对象
* @returns {boolean} - 返回是否成功处理事件
*/
onMouseoutCheck: function (event, node) {
// 如果节点被禁用则直接返回false
if (node.chkDisabled === true) return false;
// 获取当前树的设置信息和复选框元素
var setting = data.getSetting(event.data.treeId),
checkObj = $$(node, consts.id.CHECK, setting);
// 设置节点的check_Focus属性为false
node.check_Focus = false;
// 设置复选框的样式
view.setChkClass(setting, checkObj, node);
return true;
}
},
//method of tools for zTree
_tools = {},
// 操作ztree DOM的方法
_view = {
/**
* 检查节点关系,确保单选模式下只有一个节点被选中
* @param {Object} setting - ztree的配置对象
* @param {Object} node - 当前操作的节点对象
*/
checkNodeRelation: function (setting, node) {
var pNode, i, l,
r = consts.radio; // 获取常量中的单选模式配置
var nodeChecked = data.nodeChecked(setting, node); // 判断当前节点是否被选中
if (setting.check.chkStyle == r.STYLE) { // 如果配置为单选模式
var checkedList = data.getRadioCheckedList(setting); // 获取已选中的单选节点列表
if (nodeChecked) { // 如果当前节点被选中
if (setting.check.radioType == r.TYPE_ALL) { // 如果单选类型为全选
for (i = checkedList.length - 1; i >= 0; i--) { // 遍历已选中的单选节点列表
pNode = checkedList[i]; // 获取已选中的节点
var pNodeChecked = data.nodeChecked(setting, pNode); // 判断已选中的节点是否被选中
if (pNodeChecked && pNode != node) { // 如果已选中的节点被选中且不是当前节点
data.nodeChecked(setting, pNode, false); // 取消已选中节点的选中状态
checkedList.splice(i, 1); // 从已选中列表中移除该节点
view.setChkClass(setting, $$(pNode, consts.id.CHECK, setting), pNode); // 更新节点的样式
if (pNode.parentTId != node.parentTId) { // 如果父节点不同
view.repairParentChkClassWithSelf(setting, pNode); // 修复父节点的样式
}
}
}
// 将节点添加到已选中列表中
checkedList.push(node);
} else {
// 获取父节点,如果当前节点有父节点则获取其父节点,否则获取根节点
var parentNode = (node.parentTId) ? node.getParentNode() : data.getRoot(setting);
// 获取父节点的所有子节点
var children = data.nodeChildren(setting, parentNode);
// 遍历所有子节点
for (i = 0, l = children.length; i < l; i++) {
pNode = children[i];
// 检查子节点是否被选中
var pNodeChecked = data.nodeChecked(setting, pNode);
// 如果子节点被选中且不是当前节点,则取消其选中状态并更新视图
if (pNodeChecked && pNode != node) {
data.nodeChecked(setting, pNode, false);
view.setChkClass(setting, $$(pNode, consts.id.CHECK, setting), pNode);
}
}
} else if (setting.check.radioType == r.TYPE_ALL) {
// 如果设置为单选类型,则从已选中列表中移除当前节点
for (i = 0, l = checkedList.length; i < l; i++) {
if (node == checkedList[i]) {
checkedList.splice(i, 1);
break;
}
}
}
} else {
// 获取当前节点的子节点
var children = data.nodeChildren(setting, node);
// 如果当前节点被选中,并且没有子节点或子节点为空,或者设置中允许部分选中状态
if (nodeChecked && (!children || children.length == 0 || setting.check.chkboxType.Y.indexOf("s") > -1)) {
// 设置子节点的复选框为选中状态
view.setSonNodeCheckBox(setting, node, true);
}
// 如果当前节点未被选中,并且没有子节点或子节点为空,或者设置中允许部分未选中状态
if (!nodeChecked && (!children || children.length == 0 || setting.check.chkboxType.N.indexOf("s") > -1)) {
// 设置子节点的复选框为未选中状态
view.setSonNodeCheckBox(setting, node, false);
}
// 如果当前节点被选中,并且设置中允许父节点选中状态
if (nodeChecked && setting.check.chkboxType.Y.indexOf("p") > -1) {
// 设置父节点的复选框为选中状态
view.setParentNodeCheckBox(setting, node, true);
}
// 如果当前节点未被选中,并且设置中允许父节点未选中状态
if (!nodeChecked && setting.check.chkboxType.N.indexOf("p") > -1) {
// 设置父节点的复选框为未选中状态
view.setParentNodeCheckBox(setting, node, false);
}
}
},
makeChkClass: function (setting, node) {
// 获取复选框和单选按钮的常量
var c = consts.checkbox, r = consts.radio,
fullStyle = "";
// 检查节点是否被选中
var nodeChecked = data.nodeChecked(setting, node);
// 如果节点被禁用,设置样式为禁用状态
if (node.chkDisabled === true) {
fullStyle = c.DISABLED;
// 如果节点处于半选状态,设置样式为部分选中
} else if (node.halfCheck) {
fullStyle = c.PART;
// 如果设置为单选按钮样式,根据子节点状态设置样式
} else if (setting.check.chkStyle == r.STYLE) {
fullStyle = (node.check_Child_State < 1) ? c.FULL : c.PART;
// 根据节点是否选中和子节点状态设置样式
} else {
fullStyle = nodeChecked ? ((node.check_Child_State === 2 || node.check_Child_State === -1) ? c.FULL : c.PART) : ((node.check_Child_State < 1) ? c.FULL : c.PART);
}
// 生成复选框的类名
var chkName = setting.check.chkStyle + "_" + (nodeChecked ? c.TRUE : c.FALSE) + "_" + fullStyle;
// 如果节点有焦点且未被禁用,添加焦点样式
chkName = (node.check_Focus && node.chkDisabled !== true) ? chkName + "_" + c.FOCUS : chkName;
// 返回完整的类名字符串
return consts.className.BUTTON + " " + c.DEFAULT + " " + chkName;
},
/**
* 修复所有复选框的状态
* @param {Object} setting - 配置对象
* @param {Boolean} checked - 是否选中
*/
repairAllChk: function (setting, checked) {
// 如果启用了复选框并且样式为复选框样式
if (setting.check.enable && setting.check.chkStyle === consts.checkbox.STYLE) {
// 获取根节点
var root = data.getRoot(setting);
// 获取根节点的所有子节点
var children = data.nodeChildren(setting, root);
// 遍历所有子节点
for (var i = 0, l = children.length; i < l; i++) {
var node = children[i];
// 如果节点没有被禁用且没有禁止选中
if (node.nocheck !== true && node.chkDisabled !== true) {
// 设置节点的选中状态
data.nodeChecked(setting, node, checked);
}
// 更新子节点的复选框状态
view.setSonNodeCheckBox(setting, node, checked);
}
}
},
/**
* 修复单个节点的复选框样式
* @param {Object} setting - 配置对象
* @param {Object} node - 节点对象
*/
repairChkClass: function (setting, node) {
// 如果节点不存在,直接返回
if (!node) return;
// 标记节点的复选框状态
data.makeChkFlag(setting, node);
// 如果节点没有被禁止选中
if (node.nocheck !== true) {
// 获取节点的复选框对象
var checkObj = $$(node, consts.id.CHECK, setting);
// 设置复选框的样式
view.setChkClass(setting, checkObj, node);
}
},
repairParentChkClass: function (setting, node) {
// 如果节点不存在或没有父节点ID则直接返回
if (!node || !node.parentTId) return;
// 获取父节点
var pNode = node.getParentNode();
// 修复父节点的复选框样式
view.repairChkClass(setting, pNode);
// 递归修复父节点的父节点的复选框样式
view.repairParentChkClass(setting, pNode);
},
repairParentChkClassWithSelf: function (setting, node) {
// 如果节点不存在,则直接返回
if (!node) return;
// 获取子节点列表
var children = data.nodeChildren(setting, node);
// 如果存在子节点,则修复第一个子节点的父节点复选框样式
if (children && children.length > 0) {
view.repairParentChkClass(setting, children[0]);
} else {
// 否则修复当前节点的父节点复选框样式
view.repairParentChkClass(setting, node);
}
},
repairSonChkDisabled: function (setting, node, chkDisabled, inherit) {
// 如果节点不存在,则直接返回
if (!node) return;
// 如果节点的chkDisabled属性与传入值不同则更新该属性
if (node.chkDisabled != chkDisabled) {
node.chkDisabled = chkDisabled;
}
// 修复当前节点的复选框样式
view.repairChkClass(setting, node);
// 获取子节点列表
var children = data.nodeChildren(setting, node);
// 如果存在子节点且需要继承则递归修复所有子节点的chkDisabled属性和复选框样式
if (children && inherit) {
for (var i = 0, l = children.length; i < l; i++) {
var sNode = children[i];
view.repairSonChkDisabled(setting, sNode, chkDisabled, inherit);
}
}
},
repairParentChkDisabled: function (setting, node, chkDisabled, inherit) {
// 如果节点不存在,则直接返回
if (!node) return;
// 如果节点的chkDisabled属性与传入值不同且需要继承则更新该属性
if (node.chkDisabled != chkDisabled && inherit) {
node.chkDisabled = chkDisabled;
}
// 修复当前节点的复选框样式
view.repairChkClass(setting, node);
// 递归修复父节点的chkDisabled属性和复选框样式
view.repairParentChkDisabled(setting, node.getParentNode(), chkDisabled, inherit);
},
setChkClass: function (setting, obj, node) {
// 如果对象不存在,直接返回
if (!obj) return;
// 如果节点的nocheck属性为true隐藏复选框
if (node.nocheck === true) {
obj.hide();
} else {
// 否则显示复选框
obj.show();
}
// 设置复选框的class属性
obj.attr('class', view.makeChkClass(setting, node));
},
setParentNodeCheckBox: function (setting, node, value, srcNode) {
// 获取当前节点的复选框对象
var checkObj = $$(node, consts.id.CHECK, setting);
// 如果没有传入srcNode则将当前节点赋值给srcNode
if (!srcNode) srcNode = node;
// 更新节点的检查状态标志
data.makeChkFlag(setting, node);
// 如果节点没有禁用检查且未设置为不检查
if (node.nocheck !== true && node.chkDisabled !== true) {
// 设置节点的选中状态
data.nodeChecked(setting, node, value);
// 更新复选框的样式和类名
view.setChkClass(setting, checkObj, node);
// 如果自动检查触发开启且当前节点不是源节点触发CHECK事件
if (setting.check.autoCheckTrigger && node != srcNode) {
setting.treeObj.trigger(consts.event.CHECK, [null, setting.treeId, node]);
}
}
// 如果节点有父节点
if (node.parentTId) {
var pSign = true;
// 如果值为false
if (!value) {
// 获取父节点的所有子节点
var pNodes = data.nodeChildren(setting, node.getParentNode());
// 遍历所有子节点
for (var i = 0, l = pNodes.length; i < l; i++) {
var pNode = pNodes[i];
// 获取子节点的选中状态
var nodeChecked = data.nodeChecked(setting, pNode);
// 如果子节点未被禁用且选中或者子节点被禁用但有子节点被选中则pSign设为false并跳出循环
if ((pNode.nocheck !== true && pNode.chkDisabled !== true && nodeChecked)
|| ((pNode.nocheck === true || pNode.chkDisabled === true) && pNode.check_Child_State > 0)) {
pSign = false;
break;
}
}
}
if (pSign) {
// 如果 pSign 为真,设置父节点的复选框状态
view.setParentNodeCheckBox(setting, node.getParentNode(), value, srcNode);
}
},
setSonNodeCheckBox: function (setting, node, value, srcNode) {
// 如果节点不存在,直接返回
if (!node) return;
// 获取当前节点的复选框对象
var checkObj = $$(node, consts.id.CHECK, setting);
// 如果源节点未定义,则将当前节点设为源节点
if (!srcNode) srcNode = node;
// 初始化 hasDisable 标志位,用于记录子节点是否有禁用的复选框
var hasDisable = false;
// 获取当前节点的子节点列表
var children = data.nodeChildren(setting, node);
if (children) {
// 遍历所有子节点
for (var i = 0, l = children.length; i < l; i++) {
var sNode = children[i];
// 递归设置子节点的复选框状态
view.setSonNodeCheckBox(setting, sNode, value, srcNode);
// 如果子节点被禁用,则设置 hasDisable 为 true
if (sNode.chkDisabled === true) hasDisable = true;
}
}
// 如果当前节点不是根节点且未被禁用
if (node != data.getRoot(setting) && node.chkDisabled !== true) {
// 如果存在禁用的子节点且当前节点未被禁用,更新当前节点的复选框状态
if (hasDisable && node.nocheck !== true) {
data.makeChkFlag(setting, node);
}
// 如果当前节点未被禁用且未设置为不检查
if (node.nocheck !== true && node.chkDisabled !== true) {
// 设置当前节点的选中状态
data.nodeChecked(setting, node, value);
// 根据子节点的状态更新当前节点的子节点状态
if (!hasDisable) node.check_Child_State = (children && children.length > 0) ? (value ? 2 : 0) : -1;
} else {
// 如果当前节点被禁用或设置为不检查,则将其子节点状态设为 -1
node.check_Child_State = -1;
}
// 更新当前节点的复选框样式
view.setChkClass(setting, checkObj, node);
// 如果自动触发检查事件,并且当前节点不是源节点且未被禁用或设置为不检查,则触发检查事件
if (setting.check.autoCheckTrigger && node != srcNode && node.nocheck !== true && node.chkDisabled !== true) {
setting.treeObj.trigger(consts.event.CHECK, [null, setting.treeId, node]);
}
}
}
},
_z = {
tools: _tools,
view: _view,
event: _event,
data: _data
};
// 将常量对象扩展到jQuery的zTree插件中
$.extend(true, $.fn.zTree.consts, _consts);
// 将工具对象扩展到jQuery的zTree插件中
$.extend(true, $.fn.zTree._z, _z);
var zt = $.fn.zTree,
tools = zt._z.tools,
consts = zt.consts,
view = zt._z.view,
data = zt._z.data,
event = zt._z.event,
$$ = tools.$;
/**
* 设置或获取节点的选中状态
* @param {Object} setting - 配置对象
* @param {Object} node - 节点对象
* @param {boolean|string} newChecked - 新的选中状态,可以是布尔值或字符串
* @returns {boolean} - 返回节点的最终选中状态
*/
data.nodeChecked = function (setting, node, newChecked) {
// 如果节点不存在返回false
if (!node) {
return false;
}
var key = setting.data.key.checked;
// 如果newChecked被定义了
if (typeof newChecked !== 'undefined') {
// 如果newChecked是字符串类型将其转换为布尔值
if (typeof newChecked === "string") {
newChecked = tools.eqs(newChecked, "true");
}
// 将newChecked转换为布尔值并赋值给节点的key属性
newChecked = !!newChecked;
node[key] = newChecked;
} else if (typeof node[key] == "string"){
// 如果节点的key属性是字符串类型将其转换为布尔值
node[key] = tools.eqs(node[key], "true");
} else {
// 否则将节点的key属性转换为布尔值
node[key] = !!node[key];
}
// 返回节点的最终选中状态
return node[key];
};
// 调用exSetting方法传入_setting参数进行设置
data.exSetting(_setting);
// 添加初始化绑定事件传入_bindEvent作为回调函数
data.addInitBind(_bindEvent);
// 添加初始化解绑事件传入_unbindEvent作为回调函数
data.addInitUnBind(_unbindEvent);
// 添加初始化缓存传入_initCache作为回调函数
data.addInitCache(_initCache);
// 添加初始化节点传入_initNode作为回调函数
data.addInitNode(_initNode);
// 添加初始化代理传入_eventProxy作为回调函数并设置第二个参数为true
data.addInitProxy(_eventProxy, true);
// 添加初始化根节点传入_initRoot作为回调函数
data.addInitRoot(_initRoot);
// 添加在A操作之前的回调函数传入_beforeA作为回调函数
data.addBeforeA(_beforeA);
// 添加ZTree工具传入_zTreeTools作为回调函数
data.addZTreeTools(_zTreeTools);
var _createNodes = view.createNodes;
view.createNodes = function (setting, level, nodes, parentNode, index) {
// 如果存在原始的 createNodes 方法,则调用它
if (_createNodes) _createNodes.apply(view, arguments);
// 如果没有节点需要创建,直接返回
if (!nodes) return;
// 修复父节点的选中状态类
view.repairParentChkClassWithSelf(setting, parentNode);
}
var _removeNode = view.removeNode;
view.removeNode = function (setting, node) {
// 获取要删除节点的父节点
var parentNode = node.getParentNode();
// 如果存在原始的 removeNode 方法,则调用它
if (_removeNode) _removeNode.apply(view, arguments);
// 如果节点或其父节点不存在,直接返回
if (!node || !parentNode) return;
// 修复父节点的选中状态类
view.repairChkClass(setting, parentNode);
// 修复父节点的选中状态类(与上一个修复方法可能不同)
view.repairParentChkClass(setting, parentNode);
}
var _appendNodes = view.appendNodes; // 保存原始的appendNodes方法
view.appendNodes = function (setting, level, nodes, parentNode, index, initFlag, openFlag) {
// 定义新的appendNodes方法接收多个参数
var html = ""; // 初始化html变量为空字符串
if (_appendNodes) {
// 如果存在原始的appendNodes方法则调用它并传递所有参数
html = _appendNodes.apply(view, arguments);
}
if (parentNode) {
// 如果parentNode存在则调用makeChkFlag方法
data.makeChkFlag(setting, parentNode);
}
return html; // 返回生成的html内容
}
})(jQuery); // 立即执行函数传入jQuery对象
/*
* JQuery zTree exedit
* v3.5.46
* http://treejs.cn/
*
* Copyright (c) 2010 Hunter.z
*
* Licensed same as jquery - MIT License
* http://www.opensource.org/licenses/mit-license.php
*
* Date: 2020-11-21
*/
(function ($) {
// 定义exedit的默认常量
var _consts = {
event: {
// 拖拽事件
DRAG: "ztree_drag",
// 放置事件
DROP: "ztree_drop",
// 重命名事件
RENAME: "ztree_rename",
// 拖动移动事件
DRAGMOVE: "ztree_dragmove"
},
id: {
// 编辑状态ID
EDIT: "_edit",
// 输入框ID
INPUT: "_input",
// 删除按钮ID
REMOVE: "_remove"
},
move: {
// 内部移动类型
TYPE_INNER: "inner",
// 上一个节点移动类型
TYPE_PREV: "prev",
// 下一个节点移动类型
TYPE_NEXT: "next"
},
node: {
// 当前选中节点编辑状态
CURSELECTED_EDIT: "curSelectedNode_Edit",
// 临时目标树ID
TMPTARGET_TREE: "tmpTargetzTree",
// 临时目标节点ID
TMPTARGET_NODE: "tmpTargetNode"
}
},
// 定义exedit的默认设置
_setting = {
edit: {
// 是否启用编辑功能
enable: false,
// 是否在编辑时全选节点名称
editNameSelectAll: false,
// 是否显示删除按钮
showRemoveBtn: true,
// 是否显示重命名按钮
showRenameBtn: true,
// 删除按钮的标题
removeTitle: "remove",
// 重命名按钮的标题
renameTitle: "rename",
drag: {
// 自动展开触发器
autoExpandTrigger: false,
// 是否允许复制操作
isCopy: true,
// 是否允许移动操作
isMove: true,
// 是否允许移动到前一个节点
prev: true,
// 是否允许移动到下一个节点
next: true,
// 是否允许移动到内部节点
inner: true,
// 最小移动尺寸
minMoveSize: 5,
// 最大边界值
borderMax: 10,
// 最小边界值
borderMin: -5,
// 最大显示节点数
maxShowNodeNum: 5,
// 自动打开时间(毫秒)
autoOpenTime: 500
}
},
view: {
// 添加悬停DOM元素的回调函数初始值为null
addHoverDom: null,
// 移除悬停DOM元素的回调函数初始值为null
removeHoverDom: null
},
callback: {
// 拖动节点前的回调函数初始值为null
beforeDrag: null,
// 打开拖动节点前的回调函数初始值为null
beforeDragOpen: null,
// 放置节点前的回调函数初始值为null
beforeDrop: null,
// 编辑节点名称前的回调函数初始值为null
beforeEditName: null,
// 重命名节点前的回调函数初始值为null
beforeRename: null,
// 拖动节点时的回调函数初始值为null
onDrag: null,
// 拖动节点移动时的回调函数初始值为null
onDragMove: null,
// 放置节点后的回调函数初始值为null
onDrop: null,
// 重命名节点后的回调函数初始值为null
onRename: null
}
},
//default root of exedit
_initRoot = function (setting) {
// 获取根节点对象
var r = data.getRoot(setting), rs = data.getRoots();
// 当前编辑的节点设为空
r.curEditNode = null;
// 当前编辑的输入框设为空
r.curEditInput = null;
// 当前悬停的节点设为空
r.curHoverNode = null;
// 拖拽标志设为0
r.dragFlag = 0;
// 初始化拖拽显示前的节点数组
r.dragNodeShowBefore = [];
// 初始化拖拽遮罩列表
r.dragMaskList = new Array();
// 设置是否显示悬停的DOM元素
rs.showHoverDom = true;
},
//default cache of exedit
_initCache = function (treeId) {
// 初始化缓存,暂未实现具体逻辑
},
//default bind event of exedit
_bindEvent = function (setting) {
// 获取树对象
var o = setting.treeObj;
// 获取事件常量
var c = consts.event;
o.bind(c.RENAME, function (event, treeId, treeNode, isCancel) {
// 绑定重命名事件,调用回调函数
tools.apply(setting.callback.onRename, [event, treeId, treeNode, isCancel]);
});
o.bind(c.DRAG, function (event, srcEvent, treeId, treeNodes) {
// 绑定拖拽事件,调用回调函数
tools.apply(setting.callback.onDrag, [srcEvent, treeId, treeNodes]);
});
o.bind(c.DRAGMOVE, function (event, srcEvent, treeId, treeNodes) {
// 绑定拖拽移动事件,调用回调函数
tools.apply(setting.callback.onDragMove, [srcEvent, treeId, treeNodes]);
});
o.bind(c.DROP, function (event, srcEvent, treeId, treeNodes, targetNode, moveType, isCopy) {
// 调用设置中的回调函数,处理节点拖放事件
tools.apply(setting.callback.onDrop, [srcEvent, treeId, treeNodes, targetNode, moveType, isCopy]);
});
},
_unbindEvent = function (setting) {
// 获取树对象和常量
var o = setting.treeObj;
var c = consts.event;
// 解绑重命名事件
o.unbind(c.RENAME);
// 解绑拖拽开始事件
o.unbind(c.DRAG);
// 解绑拖拽移动事件
o.unbind(c.DRAGMOVE);
// 解绑拖拽放下事件
o.unbind(c.DROP);
},
//default event proxy of exedit
_eventProxy = function (e) {
// 获取事件目标、相关目标、设置等变量
var target = e.target,
setting = data.getSetting(e.data.treeId),
relatedTarget = e.relatedTarget,
tId = "", node = null,
nodeEventType = "", treeEventType = "",
nodeEventCallback = null, treeEventCallback = null,
tmp = null;
if (tools.eqs(e.type, "mouseover")) {
// 处理鼠标悬停事件获取目标节点的ID和事件类型
tmp = tools.getMDom(setting, target, [{tagName: "a", attrName: "treeNode" + consts.id.A}]);
if (tmp) {
tId = tools.getNodeMainDom(tmp).id;
nodeEventType = "hoverOverNode";
}
} else if (tools.eqs(e.type, "mouseout")) {
// 处理鼠标移出事件,判断是否移出了节点范围
tmp = tools.getMDom(setting, relatedTarget, [{tagName: "a", attrName: "treeNode" + consts.id.A}]);
if (!tmp) {
tId = "remove";
nodeEventType = "hoverOutNode";
}
} else if (tools.eqs(e.type, "mousedown")) {
// 处理鼠标按下事件获取目标节点的ID和事件类型
tmp = tools.getMDom(setting, target, [{tagName: "a", attrName: "treeNode" + consts.id.A}]);
if (tmp) {
tId = tools.getNodeMainDom(tmp).id;
nodeEventType = "mousedownNode";
}
}
// 检查 tId 数组是否非空
if (tId.length > 0) {
// 从缓存中获取节点对象
node = data.getNodeCache(setting, tId);
// 根据节点事件类型选择相应的回调函数
switch (nodeEventType) {
// 如果事件类型是鼠标按下节点
case "mousedownNode" :
// 设置回调函数为鼠标按下节点的处理函数
nodeEventCallback = _handler.onMousedownNode;
break;
// 如果事件类型是鼠标悬停到节点上
case "hoverOverNode" :
// 设置回调函数为鼠标悬停到节点上的处理函数
nodeEventCallback = _handler.onHoverOverNode;
break;
// 如果事件类型是鼠标移出节点
case "hoverOutNode" :
// 设置回调函数为鼠标移出节点的处理函数
nodeEventCallback = _handler.onHoverOutNode;
break;
}
}
var proxyResult = {
stop: false, // 初始化停止标志为false
node: node, // 当前节点
nodeEventType: nodeEventType, // 节点事件类型
nodeEventCallback: nodeEventCallback, // 节点事件回调函数
treeEventType: treeEventType, // 树事件类型
treeEventCallback: treeEventCallback // 树事件回调函数
};
return proxyResult // 返回代理结果对象
},
//default init node of exedit
_initNode = function (setting, level, n, parentNode, isFirstNode, isLastNode, openFlag) {
if (!n) return; // 如果节点不存在,直接返回
n.isHover = false; // 初始化节点的悬停状态为false
n.editNameFlag = false; // 初始化节点的编辑名称标志为false
},
//update zTreeObj, add method of edit
_zTreeTools = function (setting, zTreeTools) {
zTreeTools.cancelEditName = function (newName) {
var root = data.getRoot(this.setting); // 获取根节点
if (!root.curEditNode) return; // 如果没有正在编辑的节点,直接返回
view.cancelCurEditNode(this.setting, newName ? newName : null, true); // 取消当前编辑的节点
}
zTreeTools.copyNode = function (targetNode, node, moveType, isSilent) {
if (!node) return null; // 如果节点不存在返回null
var isParent = data.nodeIsParent(setting, targetNode); // 判断目标节点是否是父节点
if (targetNode && !isParent && this.setting.data.keep.leaf && moveType === consts.move.TYPE_INNER) return null; // 如果目标节点不是父节点且不允许移动到叶子节点返回null
var _this = this,
newNode = tools.clone(node); // 克隆新节点
if (!targetNode) {
targetNode = null; // 如果目标节点不存在设置为null
moveType = consts.move.TYPE_INNER; // 设置移动类型为内部移动
}
if (moveType == consts.move.TYPE_INNER) {
function copyCallback() {
view.addNodes(_this.setting, targetNode, -1, [newNode], isSilent); // 添加新节点到目标节点
}
if (tools.canAsync(this.setting, targetNode)) {
view.asyncNode(this.setting, targetNode, isSilent, copyCallback); // 如果目标节点支持异步加载,则异步加载后执行回调
} else {
copyCallback(); // 否则直接执行回调
}
} else {
view.addNodes(this.setting, targetNode.parentNode, -1, [newNode], isSilent); // 添加新节点到目标节点的父节点
view.moveNode(this.setting, targetNode, newNode, moveType, false, isSilent); // 移动目标节点到新节点位置
}
return newNode; // 返回新节点
}
zTreeTools.editName = function (node) {
if (!node || !node.tId || node !== data.getNodeCache(this.setting, node.tId)) return; // 如果节点不存在或节点ID无效或节点不在缓存中直接返回
if (node.parentTId) view.expandCollapseParentNode(this.setting, node.getParentNode(), true); // 如果节点有父节点,展开父节点
view.editNode(this.setting, node) // 编辑节点名称
}
zTreeTools.moveNode = function (targetNode, node, moveType, isSilent) {
if (!node) return node; // 如果节点不存在,直接返回节点
var isParent = data.nodeIsParent(setting, targetNode); // 判断目标节点是否是父节点
if (targetNode && !isParent && this.setting.data.keep.leaf && moveType === consts.move.TYPE_INNER) {
return null; // 如果目标节点不是父节点且不允许移动到叶子节点返回null
} else if (targetNode && ((node.parentTId == targetNode.tId && moveType == consts.move.TYPE_INNER) || $$(node, this.setting).find("#" + targetNode.tId).length > 0)) {
return null; // 如果目标节点是当前节点的父节点且移动类型为内部移动或者目标节点包含当前节点返回null
} else if (!targetNode) {
targetNode = null; // 如果目标节点不存在设置为null
var _this = this;
function moveCallback() {
// 调用view对象的moveNode方法执行节点移动操作
view.moveNode(_this.setting, targetNode, node, moveType, false, isSilent);
}
// 判断是否可以异步处理节点移动,并且移动类型为内部移动
if (tools.canAsync(this.setting, targetNode) && moveType === consts.move.TYPE_INNER) {
// 如果可以异步处理则调用view对象的asyncNode方法进行异步节点移动
view.asyncNode(this.setting, targetNode, isSilent, moveCallback);
} else {
// 否则直接调用moveCallback方法进行同步节点移动
moveCallback();
}
// 返回当前节点对象
return node;
}
zTreeTools.setEditable = function (editable) {
// 设置树的编辑状态
this.setting.edit.enable = editable;
// 刷新树以应用新的编辑状态
return this.refresh();
}
},
//method of operate data
_data = {
/**
* 设置子节点的层级
* @param {Object} setting - 配置对象
* @param {Object} parentNode - 父节点对象
* @param {Object} node - 当前节点对象
*/
setSonNodeLevel: function (setting, parentNode, node) {
// 如果当前节点不存在,直接返回
if (!node) return;
// 获取当前节点的子节点列表
var children = data.nodeChildren(setting, node);
// 设置当前节点的层级如果存在父节点则层级加1否则为0
node.level = (parentNode) ? parentNode.level + 1 : 0;
// 如果子节点列表不存在,直接返回
if (!children) return;
// 遍历所有子节点,递归设置每个子节点的层级
for (var i = 0, l = children.length; i < l; i++) {
if (children[i]) data.setSonNodeLevel(setting, node, children[i]);
}
}
},
//method of event proxy
_event = {},
// 事件处理程序的方法
_handler = {
/**
* 当鼠标悬停在节点上时触发的事件处理函数
* @param {Object} event - 事件对象,包含事件相关的数据
* @param {Object} node - 当前悬停的节点对象
*/
onHoverOverNode: function (event, node) {
// 获取树的设置信息
var setting = data.getSetting(event.data.treeId),
// 获取树的根节点
root = data.getRoot(setting);
// 如果当前悬停的节点不是目标节点
if (root.curHoverNode != node) {
// 调用onHoverOutNode方法处理之前的悬停节点
_handler.onHoverOutNode(event);
}
// 更新当前悬停的节点为目标节点
root.curHoverNode = node;
// 在视图中添加悬停效果的DOM元素
view.addHoverDom(setting, node);
},
/**
* 当鼠标移出节点时触发的事件处理函数
* @param {Object} event - 事件对象,包含事件相关的数据
* @param {Object} node - 当前移出的节点对象(可选)
*/
onHoverOutNode: function (event, node) {
// 获取树的设置信息
var setting = data.getSetting(event.data.treeId),
// 获取树的根节点
root = data.getRoot(setting);
// 如果当前有悬停的节点且该节点未被选中
if (root.curHoverNode && !data.isSelectedNode(setting, root.curHoverNode)) {
// 从视图中移除悬停效果的DOM元素
view.removeTreeDom(setting, root.curHoverNode);
// 清空当前悬停的节点
root.curHoverNode = null;
}
},
onMousedownNode: function (eventMouseDown, _node) {
var i, l,
setting = data.getSetting(eventMouseDown.data.treeId),
root = data.getRoot(setting), roots = data.getRoots();
//right click can't drag & drop
if (eventMouseDown.button == 2 || !setting.edit.enable || (!setting.edit.drag.isCopy && !setting.edit.drag.isMove)) return true;
//input of edit node name can't drag & drop
var target = eventMouseDown.target,
_nodes = data.getRoot(setting).curSelectedList,
nodes = [];
if (!data.isSelectedNode(setting, _node)) {
nodes = [_node];
} else {
for (i = 0, l = _nodes.length; i < l; i++) {
if (_nodes[i].editNameFlag && tools.eqs(target.tagName, "input") && target.getAttribute("treeNode" + consts.id.INPUT) !== null) {
return true;
}
nodes.push(_nodes[i]);
if (nodes[0].parentTId !== _nodes[i].parentTId) {
nodes = [_node];
break;
}
}
}
view.editNodeBlur = true; // 设置编辑节点失去焦点标志为true
view.cancelCurEditNode(setting); // 取消当前正在编辑的节点
// 获取文档对象和body对象
var doc = $(setting.treeObj.get(0).ownerDocument),
body = $(setting.treeObj.get(0).ownerDocument.body), curNode, tmpArrow, tmpTarget,
isOtherTree = false, // 标记是否为其他树
targetSetting = setting, // 目标设置
sourceSetting = setting, // 源设置
preNode, nextNode, // 前一个节点和后一个节点
preTmpTargetNodeId = null, // 临时目标节点ID
preTmpMoveType = null, // 临时移动类型
tmpTargetNodeId = null, // 临时目标节点ID
moveType = consts.move.TYPE_INNER, // 移动类型,默认为内部移动
mouseDownX = eventMouseDown.clientX, // 鼠标按下时的X坐标
mouseDownY = eventMouseDown.clientY, // 鼠标按下时的Y坐标
startTime = (new Date()).getTime(); // 记录开始时间
if (tools.uCanDo(setting)) { // 如果可以执行操作
doc.bind("mousemove", _docMouseMove); // 绑定鼠标移动事件到文档上
}
function _docMouseMove(event) {
// 避免在点击节点后开始拖动
if (root.dragFlag == 0 && Math.abs(mouseDownX - event.clientX) < setting.edit.drag.minMoveSize
&& Math.abs(mouseDownY - event.clientY) < setting.edit.drag.minMoveSize) {
return true; // 如果鼠标移动距离小于最小拖动距离,则不进行拖动操作
}
var i, l, tmpNode, tmpDom, tmpNodes;
body.css("cursor", "pointer"); // 将鼠标指针样式设置为指针
if (root.dragFlag == 0) {
// 检查是否允许拖动操作
if (tools.apply(setting.callback.beforeDrag, [setting.treeId, nodes], true) == false) {
_docMouseUp(event); // 如果不允许拖动则调用_docMouseUp方法结束拖动
return true; // 返回true表示处理完成
}
// 遍历所有节点
for (i = 0, l = nodes.length; i < l; i++) {
// 如果是第一个节点初始化dragNodeShowBefore数组
if (i == 0) {
root.dragNodeShowBefore = [];
}
// 获取当前节点
tmpNode = nodes[i];
// 如果当前节点是父节点且处于展开状态
if (data.nodeIsParent(setting, tmpNode) && tmpNode.open) {
// 折叠该节点
view.expandCollapseNode(setting, tmpNode, !tmpNode.open);
// 记录该节点在拖拽前的状态为展开
root.dragNodeShowBefore[tmpNode.tId] = true;
} else {
// 记录该节点在拖拽前的状态为折叠
root.dragNodeShowBefore[tmpNode.tId] = false;
}
}
// 设置拖拽标志位为1
root.dragFlag = 1;
// 隐藏悬停的DOM元素
roots.showHoverDom = false;
// 显示iframe遮罩层
tools.showIfameMask(setting, true);
// 排序逻辑
var isOrder = true, lastIndex = -1;
if (nodes.length > 1) {
// 获取父节点或根节点的子节点列表
var pNodes = nodes[0].parentTId ? data.nodeChildren(setting, nodes[0].getParentNode()) : data.getNodes(setting);
tmpNodes = [];
// 遍历父节点或根节点的子节点列表
for (i = 0, l = pNodes.length; i < l; i++) {
// 如果当前节点在dragNodeShowBefore中有记录
if (root.dragNodeShowBefore[pNodes[i].tId] !== undefined) {
// 检查顺序是否被打乱
if (isOrder && lastIndex > -1 && (lastIndex + 1) !== i) {
isOrder = false;
}
// 将当前节点添加到临时节点数组中
tmpNodes.push(pNodes[i]);
// 更新最后一个索引值
lastIndex = i;
}
// 如果临时节点数组的长度与原节点数组长度相同,则替换原节点数组并退出循环
if (nodes.length === tmpNodes.length) {
nodes = tmpNodes;
break;
}
}
}
if (isOrder) {
// 获取第一个节点的前一个节点
preNode = nodes[0].getPreNode();
// 获取最后一个节点的下一个节点
nextNode = nodes[nodes.length - 1].getNextNode();
}
// 创建一个新的<ul>元素,用于显示拖拽的节点
curNode = $$("<ul class='zTreeDragUL'></ul>", setting);
for (i = 0, l = nodes.length; i < l; i++) {
// 遍历所有选中的节点
tmpNode = nodes[i];
// 设置当前节点的编辑名称标志为false
tmpNode.editNameFlag = false;
// 在视图中选择当前节点如果i大于0则表示不是第一个节点
view.selectNode(setting, tmpNode, i > 0);
// 从视图中移除当前节点的DOM元素
view.removeTreeDom(setting, tmpNode);
if (i > setting.edit.drag.maxShowNodeNum - 1) {
// 如果当前节点索引超过最大显示节点数,则跳过该节点
continue;
}
// 创建一个临时的<li>元素并设置其id为tmpNode.tId + "_tmp"
tmpDom = $$("<li id='" + tmpNode.tId + "_tmp'></li>", setting);
// 将tmpNode节点克隆后添加到临时<li>元素中
tmpDom.append($$(tmpNode, consts.id.A, setting).clone());
// 设置临时<li>元素的内边距为0
tmpDom.css("padding", "0");
// 移除临时<li>元素中子节点的CURSELECTED类
tmpDom.children("#" + tmpNode.tId + consts.id.A).removeClass(consts.node.CURSELECTED);
// 将临时<li>元素添加到当前节点curNode中
curNode.append(tmpDom);
// 如果当前是最后一个需要显示的节点
if (i == setting.edit.drag.maxShowNodeNum - 1) {
// 创建一个包含省略号的<li>元素并设置其id为tmpNode.tId + "_moretmp"
tmpDom = $$("<li id='" + tmpNode.tId + "_moretmp'><a> ... </a></li>", setting);
// 将该<li>元素添加到当前节点curNode中
curNode.append(tmpDom);
}
// 设置当前节点curNode的id为nodes[0].tId + consts.id.UL + "_tmp"
curNode.attr("id", nodes[0].tId + consts.id.UL + "_tmp");
// 给当前节点curNode添加与treeObj相同的class属性
curNode.addClass(setting.treeObj.attr("class"));
// 将当前节点curNode添加到body中
curNode.appendTo(body);
// 创建一个带有箭头样式的<span>元素并设置其id为zTreeMove_arrow_tmp
tmpArrow = $$("<span class='tmpzTreeMove_arrow'></span>", setting);
tmpArrow.attr("id", "zTreeMove_arrow_tmp");
// 将箭头<span>元素添加到body中
tmpArrow.appendTo(body);
// 触发DRAG事件传递事件对象、树ID和节点数组作为参数
setting.treeObj.trigger(consts.event.DRAG, [event, setting.treeId, nodes]);
if (root.dragFlag == 1) {
// 如果拖拽标志为1表示正在进行拖拽操作
if (tmpTarget && tmpArrow.attr("id") == event.target.id && tmpTargetNodeId && (event.clientX + doc.scrollLeft() + 2) > ($("#" + tmpTargetNodeId + consts.id.A, tmpTarget).offset().left)) {
// 如果临时目标存在且箭头ID与事件目标ID相同并且临时目标节点ID存在且鼠标位置在临时目标节点的左边距内
var xT = $("#" + tmpTargetNodeId + consts.id.A, tmpTarget);
// 获取临时目标节点的左箭头元素
event.target = (xT.length > 0) ? xT.get(0) : event.target;
// 如果左箭头元素存在,则将事件目标设置为该元素,否则保持原事件目标不变
} else if (tmpTarget) {
// 如果临时目标存在但不符合上述条件
tmpTarget.removeClass(consts.node.TMPTARGET_TREE);
// 移除临时目标上的临时目标树类
if (tmpTargetNodeId) $("#" + tmpTargetNodeId + consts.id.A, tmpTarget).removeClass(consts.node.TMPTARGET_NODE + "_" + consts.move.TYPE_PREV)
.removeClass(consts.node.TMPTARGET_NODE + "_" + _consts.move.TYPE_NEXT).removeClass(consts.node.TMPTARGET_NODE + "_" + _consts.move.TYPE_INNER);
// 移除临时目标节点上的临时目标节点类(前、后、内部)
}
tmpTarget = null;
// 清空临时目标
tmpTargetNodeId = null;
// 清空临时目标节点ID
// 判断是否在多个ztree中进行拖放操作
isOtherTree = false;
// 初始化为不在其它树中
targetSetting = setting;
// 设置当前配置为目标配置
var settings = data.getSettings();
// 获取所有树的配置信息
for (var s in settings) {
// 遍历所有配置信息
if (settings[s].treeId && settings[s].edit.enable && settings[s].treeId != setting.treeId
&& (event.target.id == settings[s].treeId || $(event.target).parents("#" + settings[s].treeId).length > 0)) {
// 如果配置中的树ID存在且编辑功能启用且树ID不同于当前树ID并且事件目标ID等于配置中的树ID或事件目标的父元素包含配置中的树ID
isOtherTree = true;
// 标记为在其它树中
targetSetting = settings[s];
// 更新目标配置为当前配置
}
}
// 获取文档的垂直滚动距离
var docScrollTop = doc.scrollTop(),
// 获取文档的水平滚动距离
docScrollLeft = doc.scrollLeft(),
// 获取目标树形结构的偏移位置
treeOffset = targetSetting.treeObj.offset(),
// 获取目标树形结构的高度
scrollHeight = targetSetting.treeObj.get(0).scrollHeight,
// 获取目标树形结构的宽度
scrollWidth = targetSetting.treeObj.get(0).scrollWidth,
// 计算鼠标指针到树形结构顶部的距离
dTop = (event.clientY + docScrollTop - treeOffset.top),
// 计算鼠标指针到树形结构底部的距离
dBottom = (targetSetting.treeObj.height() + treeOffset.top - event.clientY - docScrollTop),
// 计算鼠标指针到树形结构左侧的距离
dLeft = (event.clientX + docScrollLeft - treeOffset.left),
// 计算鼠标指针到树形结构右侧的距离
dRight = (targetSetting.treeObj.width() + treeOffset.left - event.clientX - docScrollLeft),
// 判断鼠标指针是否在树形结构的上边界范围内
isTop = (dTop < setting.edit.drag.borderMax && dTop > setting.edit.drag.borderMin),
// 判断鼠标指针是否在树形结构的下边界范围内
isBottom = (dBottom < setting.edit.drag.borderMax && dBottom > setting.edit.drag.borderMin),
// 判断鼠标指针是否在树形结构的左边界范围内
isLeft = (dLeft < setting.edit.drag.borderMax && dLeft > setting.edit.drag.borderMin),
// 判断鼠标指针是否在树形结构的右边界范围内
isRight = (dRight < setting.edit.drag.borderMax && dRight > setting.edit.drag.borderMin),
// 判断鼠标指针是否在树形结构的内部区域
isTreeInner = dTop > setting.edit.drag.borderMin && dBottom > setting.edit.drag.borderMin && dLeft > setting.edit.drag.borderMin && dRight > setting.edit.drag.borderMin,
// 判断鼠标指针是否在树形结构的顶部且树形结构已滚动到顶部
isTreeTop = (isTop && targetSetting.treeObj.scrollTop() <= 0),
// 判断鼠标指针是否在树形结构的底部且树形结构已滚动到底部
isTreeBottom = (isBottom && (targetSetting.treeObj.scrollTop() + targetSetting.treeObj.height() + 10) >= scrollHeight),
// 判断鼠标指针是否在树形结构的左侧且树形结构已滚动到最左侧
isTreeLeft = (isLeft && targetSetting.treeObj.scrollLeft() <= 0),
// 判断鼠标指针是否在树形结构的右侧且树形结构已滚动到最右侧
isTreeRight = (isRight && (targetSetting.treeObj.scrollLeft() + targetSetting.treeObj.width() + 10) >= scrollWidth);
// 如果事件目标存在并且是目标树形结构或其子元素
if (event.target && tools.isChildOrSelf(event.target, targetSetting.treeId)) {
// 获取节点<li>的DOM对象
var targetObj = event.target;
// 循环查找父节点,直到找到<li>标签或者达到树形结构的根节点
while (targetObj && targetObj.tagName && !tools.eqs(targetObj.tagName, "li") && targetObj.id != targetSetting.treeId) {
targetObj = targetObj.parentNode;
}
var canMove = true; // 初始化变量canMove为true表示可以移动
// 遍历所有节点,检查目标对象是否可以移动
for (i = 0, l = nodes.length; i < l; i++) {
tmpNode = nodes[i]; // 获取当前节点
if (targetObj.id === tmpNode.tId) { // 如果目标对象的ID与当前节点的ID相同
canMove = false; // 设置canMove为false表示不能移动
break; // 退出循环
} else if ($$(tmpNode, setting).find("#" + targetObj.id).length > 0) { // 如果当前节点包含目标对象的子节点
canMove = false; // 设置canMove为false表示不能移动
break; // 退出循环
}
}
// 如果可以移动并且事件目标存在且是目标对象的子元素或自身
if (canMove && event.target && tools.isChildOrSelf(event.target, targetObj.id + consts.id.A)) {
tmpTarget = $(targetObj); // 将目标对象转换为jQuery对象并赋值给tmpTarget
tmpTargetNodeId = targetObj.id; // 获取目标对象的ID并赋值给tmpTargetNodeId
}
}
//the mouse must be in zTree
tmpNode = nodes[0];
// 获取第一个节点
if (isTreeInner && tools.isChildOrSelf(event.target, targetSetting.treeId)) {
// 判断鼠标是否在ztree的根节点内
if (!tmpTarget && (event.target.id == targetSetting.treeId || isTreeTop || isTreeBottom || isTreeLeft || isTreeRight) && (isOtherTree || (!isOtherTree && tmpNode.parentTId))) {
// 如果目标为空且鼠标在树的顶部、底部、左侧或右侧,并且是其他树或者当前节点有父节点
tmpTarget = targetSetting.treeObj;
// 设置临时目标为树对象
}
// 自动滚动到顶部
if (isTop) {
targetSetting.treeObj.scrollTop(targetSetting.treeObj.scrollTop() - 10);
// 向上滚动10个像素
} else if (isBottom) {
targetSetting.treeObj.scrollTop(targetSetting.treeObj.scrollTop() + 10);
// 向下滚动10个像素
}
if (isLeft) {
targetSetting.treeObj.scrollLeft(targetSetting.treeObj.scrollLeft() - 10);
// 向左滚动10个像素
} else if (isRight) {
targetSetting.treeObj.scrollLeft(targetSetting.treeObj.scrollLeft() + 10);
// 向右滚动10个像素
}
// 自动滚动到左侧
if (tmpTarget && tmpTarget != targetSetting.treeObj && tmpTarget.offset().left < targetSetting.treeObj.offset().left) {
targetSetting.treeObj.scrollLeft(targetSetting.treeObj.scrollLeft() + tmpTarget.offset().left - targetSetting.treeObj.offset().left);
// 根据临时目标的位置调整滚动条位置
}
}
curNode.css({
"top": (event.clientY + docScrollTop + 3) + "px",
"left": (event.clientX + docScrollLeft + 3) + "px"
});
// 设置当前节点的CSS样式使其跟随鼠标移动并偏移3个像素
var dX = 0; // 初始化水平方向的偏移量
var dY = 0; // 初始化垂直方向的偏移量
// 如果临时目标节点存在且其ID不等于目标设置中的树ID
if (tmpTarget && tmpTarget.attr("id") != targetSetting.treeId) {
// 获取临时目标节点如果tmpTargetNodeId为空则返回null
var tmpTargetNode = tmpTargetNodeId == null ? null : data.getNodeCache(targetSetting, tmpTargetNodeId),
// 判断是否为复制操作
isCopy = ((event.ctrlKey || event.metaKey) && setting.edit.drag.isMove && setting.edit.drag.isCopy) || (!setting.edit.drag.isMove && setting.edit.drag.isCopy),
// 判断是否为前一个节点
isPrev = !!(preNode && tmpTargetNodeId === preNode.tId),
// 判断是否为后一个节点
isNext = !!(nextNode && tmpTargetNodeId === nextNode.tId),
// 判断是否为内部节点
isInner = (tmpNode.parentTId && tmpNode.parentTId == tmpTargetNodeId),
// 判断是否可以移动到前一个节点
canPrev = (isCopy || !isNext) && tools.apply(targetSetting.edit.drag.prev, [targetSetting.treeId, nodes, tmpTargetNode], !!targetSetting.edit.drag.prev),
// 判断是否可以移动到后一个节点
canNext = (isCopy || !isPrev) && tools.apply(targetSetting.edit.drag.next, [targetSetting.treeId, nodes, tmpTargetNode], !!targetSetting.edit.drag.next),
// 判断是否可以移动到内部节点
canInner = (isCopy || !isInner) && !(targetSetting.data.keep.leaf && !data.nodeIsParent(setting, tmpTargetNode)) && tools.apply(targetSetting.edit.drag.inner, [targetSetting.treeId, nodes, tmpTargetNode], !!targetSetting.edit.drag.inner);
// 清除移动状态的函数
function clearMove() {
tmpTarget = null; // 清空临时目标节点
tmpTargetNodeId = ""; // 清空临时目标节点ID
moveType = consts.move.TYPE_INNER; // 重置移动类型为内部移动
tmpArrow.css({
"display": "none" // 隐藏临时箭头
});
// 如果存在zTreeMoveTimer定时器则清除它并重置目标节点ID
if (window.zTreeMoveTimer) {
clearTimeout(window.zTreeMoveTimer);
window.zTreeMoveTargetNodeTId = null
}
}
if (!canPrev && !canNext && !canInner) {
// 如果既不能向前移动,也不能向后移动,也不能内部移动,则清除移动状态
clearMove();
} else {
// 获取目标节点的A元素
var tmpTargetA = $("#" + tmpTargetNodeId + consts.id.A, tmpTarget),
// 获取下一个节点的A元素如果当前节点是最后一个节点则为null
tmpNextA = tmpTargetNode.isLastNode ? null : $("#" + tmpTargetNode.getNextNode().tId + consts.id.A, tmpTarget.next()),
// 获取目标节点A元素的顶部偏移量
tmpTop = tmpTargetA.offset().top,
// 获取目标节点A元素的左侧偏移量
tmpLeft = tmpTargetA.offset().left,
// 根据条件计算前一个节点的百分比位置
prevPercent = canPrev ? (canInner ? 0.25 : (canNext ? 0.5 : 1)) : -1,
// 根据条件计算下一个节点的百分比位置
nextPercent = canNext ? (canInner ? 0.75 : (canPrev ? 0.5 : 0)) : -1,
// 计算鼠标Y坐标与目标节点顶部偏移量的百分比
dY_percent = (event.clientY + docScrollTop - tmpTop) / tmpTargetA.height();
// 如果满足前一个节点的条件并且可以向前移动
if ((prevPercent == 1 || dY_percent <= prevPercent && dY_percent >= -.2) && canPrev) {
// 设置箭头X方向的位置
dX = 1 - tmpArrow.width();
// 设置箭头Y方向的位置
dY = tmpTop - tmpArrow.height() / 2;
// 设置移动类型为向前移动
moveType = consts.move.TYPE_PREV;
} else if ((nextPercent == 0 || dY_percent >= nextPercent && dY_percent <= 1.2) && canNext) {
// 如果满足下一个节点的条件并且可以向后移动
// 设置箭头X方向的位置
dX = 1 - tmpArrow.width();
// 设置箭头Y方向的位置根据是否有下一个节点和是否展开来决定
dY = (tmpNextA == null || (data.nodeIsParent(setting, tmpTargetNode) && tmpTargetNode.open)) ? (tmpTop + tmpTargetA.height() - tmpArrow.height() / 2) : (tmpNextA.offset().top - tmpArrow.height() / 2);
// 设置移动类型为向后移动
moveType = consts.move.TYPE_NEXT;
} else if (canInner) {
// 如果可以内部移动
// 设置箭头X方向的位置
dX = 5 - tmpArrow.width();
// 设置箭头Y方向的位置
dY = tmpTop;
// 设置移动类型为内部移动
moveType = consts.move.TYPE_INNER;
} else {
// 如果以上条件都不满足,则清除移动状态
clearMove();
}
}
if (tmpTarget) {
// 设置临时箭头的CSS样式使其显示并定位到目标节点的位置
tmpArrow.css({
"display": "block",
"top": dY + "px",
"left": (tmpLeft + dX) + "px"
});
// 为目标节点添加临时目标节点的类名
tmpTargetA.addClass(consts.node.TMPTARGET_NODE + "_" + moveType);
// 如果之前的临时目标节点ID或移动类型与当前不同则记录开始时间
if (preTmpTargetNodeId != tmpTargetNodeId || preTmpMoveType != moveType) {
startTime = (new Date()).getTime();
}
// 如果目标节点存在且是父节点并且移动类型为内部移动
if (tmpTargetNode && data.nodeIsParent(setting, tmpTargetNode) && moveType == consts.move.TYPE_INNER) {
var startTimer = true;
// 如果已有定时器且目标节点ID不同则清除现有定时器
if (window.zTreeMoveTimer && window.zTreeMoveTargetNodeTId !== tmpTargetNode.tId) {
clearTimeout(window.zTreeMoveTimer);
window.zTreeMoveTargetNodeTId = null;
} else if (window.zTreeMoveTimer && window.zTreeMoveTargetNodeTId === tmpTargetNode.tId) {
// 如果已有定时器且目标节点ID相同则不启动新的定时器
startTimer = false;
}
// 如果需要启动定时器
if (startTimer) {
// 设置定时器,在指定时间后执行自动展开操作
window.zTreeMoveTimer = setTimeout(function () {
// 如果移动类型不是内部移动,直接返回
if (moveType != consts.move.TYPE_INNER) return;
// 如果目标节点存在且是父节点且未打开,并且经过的时间超过自动打开时间,并且满足回调条件
if (tmpTargetNode && data.nodeIsParent(setting, tmpTargetNode) && !tmpTargetNode.open && (new Date()).getTime() - startTime > targetSetting.edit.drag.autoOpenTime
&& tools.apply(targetSetting.callback.beforeDragOpen, [targetSetting.treeId, tmpTargetNode], true)) {
// 切换节点状态(展开)
view.switchNode(targetSetting, tmpTargetNode);
// 如果配置了自动展开触发器,则触发展开事件
if (targetSetting.edit.drag.autoExpandTrigger) {
targetSetting.treeObj.trigger(consts.event.EXPAND, [targetSetting.treeId, tmpTargetNode]);
}
}
}, targetSetting.edit.drag.autoOpenTime + 50); // 延迟时间为自动打开时间加50毫秒
// 记录当前目标节点ID
window.zTreeMoveTargetNodeTId = tmpTargetNode.tId;
}
}
}
else {
// 设置移动类型为内部移动
moveType = consts.move.TYPE_INNER;
// 如果临时目标存在,并且应用了拖拽的内部设置函数
if (tmpTarget && tools.apply(targetSetting.edit.drag.inner, [targetSetting.treeId, nodes, null], !!targetSetting.edit.drag.inner)) {
// 给临时目标添加一个特定的类名
tmpTarget.addClass(consts.node.TMPTARGET_TREE);
} else {
// 否则将临时目标设为null
tmpTarget = null;
}
// 隐藏临时箭头
tmpArrow.css({
"display": "none"
});
// 如果存在zTreeMoveTimer定时器清除它并重置目标节点ID
if (window.zTreeMoveTimer) {
clearTimeout(window.zTreeMoveTimer);
window.zTreeMoveTargetNodeTId = null;
}
}
// 更新前一个临时目标节点ID和移动类型
preTmpTargetNodeId = tmpTargetNodeId;
preTmpMoveType = moveType;
// 触发拖动移动事件
setting.treeObj.trigger(consts.event.DRAGMOVE, [event, setting.treeId, nodes]);
// 返回false以阻止默认行为
return false;
// 绑定鼠标抬起事件到文档对象上当鼠标抬起时调用_docMouseUp函数
doc.bind("mouseup", _docMouseUp);
/**
* 处理鼠标抬起事件的函数
* @param {Event} event - 鼠标抬起事件对象
*/
function _docMouseUp(event) {
// 如果存在zTreeMoveTimer定时器则清除它并重置相关变量
if (window.zTreeMoveTimer) {
clearTimeout(window.zTreeMoveTimer);
window.zTreeMoveTargetNodeTId = null;
}
// 重置临时目标节点ID和移动类型
preTmpTargetNodeId = null;
preTmpMoveType = null;
// 解绑鼠标移动、鼠标抬起和选择开始事件
doc.unbind("mousemove", _docMouseMove);
doc.unbind("mouseup", _docMouseUp);
doc.unbind("selectstart", _docSelect);
// 将光标样式重置为空
body.css("cursor", "");
// 如果存在临时目标节点,移除其临时目标样式
if (tmpTarget) {
tmpTarget.removeClass(consts.node.TMPTARGET_TREE);
// 如果存在临时目标节点ID移除其对应的临时目标节点样式
if (tmpTargetNodeId) $("#" + tmpTargetNodeId + consts.id.A, tmpTarget).removeClass(consts.node.TMPTARGET_NODE + "_" + consts.move.TYPE_PREV)
.removeClass(consts.node.TMPTARGET_NODE + "_" + _consts.move.TYPE_NEXT).removeClass(consts.node.TMPTARGET_NODE + "_" + _consts.move.TYPE_INNER);
}
// 隐藏iframe遮罩层
tools.showIfameMask(setting, false);
// 显示悬停的DOM元素
roots.showHoverDom = true;
// 如果拖拽标志为0直接返回
if (root.dragFlag == 0) return;
// 重置拖拽标志
root.dragFlag = 0;
var i, l, tmpNode;
// 遍历所有节点,如果节点是父节点且之前被拖拽显示,并且当前未展开,则展开该节点
for (i = 0, l = nodes.length; i < l; i++) {
tmpNode = nodes[i];
if (data.nodeIsParent(setting, tmpNode) && root.dragNodeShowBefore[tmpNode.tId] && !tmpNode.open) {
view.expandCollapseNode(setting, tmpNode, !tmpNode.open);
delete root.dragNodeShowBefore[tmpNode.tId];
}
}
// 如果当前节点存在,则移除当前节点
if (curNode) curNode.remove();
// 如果临时箭头存在,则移除临时箭头
if (tmpArrow) tmpArrow.remove();
// 判断是否为复制操作根据Ctrl或Meta键和设置中的拖拽选项决定
var isCopy = ((event.ctrlKey || event.metaKey) && setting.edit.drag.isMove && setting.edit.drag.isCopy) || (!setting.edit.drag.isMove && setting.edit.drag.isCopy);
// 如果不是复制操作且目标节点存在并且目标节点ID与第一个节点的父节点ID相同并且移动类型为内部移动则将目标节点设为null
if (!isCopy && tmpTarget && tmpTargetNodeId && nodes[0].parentTId && tmpTargetNodeId == nodes[0].parentTId && moveType == consts.move.TYPE_INNER) {
tmpTarget = null;
}
// 如果目标节点存在
if (tmpTarget) {
// 获取拖拽目标节点如果目标节点ID为空则为null否则从缓存中获取节点
var dragTargetNode = tmpTargetNodeId == null ? null : data.getNodeCache(targetSetting, tmpTargetNodeId);
// 调用beforeDrop回调函数如果返回false则选择源设置中的节点并返回
if (tools.apply(setting.callback.beforeDrop, [targetSetting.treeId, nodes, dragTargetNode, moveType, isCopy], true) == false) {
view.selectNodes(sourceSetting, nodes);
return;
}
// 根据是否为复制操作,克隆节点或直接使用原节点
var newNodes = isCopy ? tools.clone(nodes) : nodes;
function dropCallback() {
// 检查是否为其他树
if (isOtherTree) {
// 如果不是复制操作,移除节点
if (!isCopy) {
for (var i = 0, l = nodes.length; i < l; i++) {
view.removeNode(setting, nodes[i]);
}
}
// 根据移动类型添加节点到目标位置
if (moveType == consts.move.TYPE_INNER) {
view.addNodes(targetSetting, dragTargetNode, -1, newNodes);
} else {
view.addNodes(targetSetting, dragTargetNode.getParentNode(), moveType == consts.move.TYPE_PREV ? dragTargetNode.getIndex() : dragTargetNode.getIndex() + 1, newNodes);
}
} else {
// 如果是复制操作且移动类型为内部,添加节点到目标位置
if (isCopy && moveType == consts.move.TYPE_INNER) {
view.addNodes(targetSetting, dragTargetNode, -1, newNodes);
} else if (isCopy) {
// 如果是复制操作,根据移动类型添加节点到目标位置
view.addNodes(targetSetting, dragTargetNode.getParentNode(), moveType == consts.move.TYPE_PREV ? dragTargetNode.getIndex() : dragTargetNode.getIndex() + 1, newNodes);
} else {
// 如果不是复制操作,根据移动类型移动节点
if (moveType != consts.move.TYPE_NEXT) {
for (i = 0, l = newNodes.length; i < l; i++) {
view.moveNode(targetSetting, dragTargetNode, newNodes[i], moveType, false);
}
} else {
// 如果移动类型为下一个,反向遍历并移动节点
for (i = -1, l = newNodes.length - 1; i < l; l--) {
view.moveNode(targetSetting, dragTargetNode, newNodes[l], moveType, false);
}
}
}
}
// 选择新添加的节点
view.selectNodes(targetSetting, newNodes);
}
var a = $$(newNodes[0], setting).get(0); // 获取新节点的第一个元素
view.scrollIntoView(setting, a); // 将视图滚动到新节点的位置
// 触发DROP事件传递相关参数事件对象、目标树ID、新节点、拖动目标节点、移动类型和是否复制
setting.treeObj.trigger(consts.event.DROP, [event, targetSetting.treeId, newNodes, dragTargetNode, moveType, isCopy]);
if (moveType == consts.move.TYPE_INNER && tools.canAsync(targetSetting, dragTargetNode)) {
// 如果移动类型是内部移动且可以异步加载节点,则异步加载节点
view.asyncNode(targetSetting, dragTargetNode, false, dropCallback);
} else {
// 否则直接调用dropCallback函数
dropCallback();
}
} else {
// 选择源设置中的节点
view.selectNodes(sourceSetting, nodes);
// 触发DROP事件传递相关参数事件对象、源树ID、节点、null、null、null
setting.treeObj.trigger(consts.event.DROP, [event, setting.treeId, nodes, null, null, null]);
}
doc.bind("selectstart", _docSelect); // 绑定selectstart事件到_docSelect函数
function _docSelect() {
// 返回false避免文档被选中
return false;
}
// 2018-03-30 FireFox已经修复了这个问题。
// 避免FireFox的Bug
// 如果zTree Div CSS设置了'overflow'拖动节点到zTree外部并且event.target会出错。
// if(eventMouseDown.preventDefault) {
// eventMouseDown.preventDefault(); // 阻止默认事件行为
// }
// 返回true表示操作成功
return true;
}
},
//method of tools for zTree
_tools = {
/**
* 获取元素的绝对位置
* @param {Object} obj - 需要获取位置的DOM元素
* @returns {Array} 返回包含元素左上角相对于文档的X和Y坐标的数组
*/
getAbs: function (obj) {
// 获取元素的边界矩形对象
var oRect = obj.getBoundingClientRect(),
// 获取页面垂直滚动的距离
scrollTop = document.body.scrollTop + document.documentElement.scrollTop,
// 获取页面水平滚动的距离
scrollLeft = document.body.scrollLeft + document.documentElement.scrollLeft;
// 返回元素的绝对位置
return [oRect.left + scrollLeft, oRect.top + scrollTop];
},
/**
* 使输入框获得焦点并设置光标位置到文本末尾
* @param {Object} inputObj - 需要聚焦的输入框对象
*/
inputFocus: function (inputObj) {
// 检查输入框是否存在
if (inputObj.get(0)) {
// 使输入框获得焦点
inputObj.focus();
// 设置光标位置到文本末尾
tools.setCursorPosition(inputObj.get(0), inputObj.val().length);
}
},
/**
* 使输入框获得焦点并选中所有文本
* @param {Object} inputObj - 需要聚焦的输入框对象
*/
inputSelect: function (inputObj) {
// 检查输入框是否存在
if (inputObj.get(0)) {
// 使输入框获得焦点
inputObj.focus();
// 选中输入框中的所有文本
inputObj.select();
}
},
/**
* 设置光标位置的函数
* @param {HTMLElement} obj - 需要设置光标位置的元素对象
* @param {number} pos - 光标要设置的位置
*/
setCursorPosition: function (obj, pos) {
// 如果元素支持 setSelectionRange 方法
if (obj.setSelectionRange) {
// 使元素获得焦点
obj.focus();
// 设置光标的起始和结束位置,使其在指定位置
obj.setSelectionRange(pos, pos);
// 如果元素不支持 setSelectionRange 但支持 createTextRange 方法
} else if (obj.createTextRange) {
// 创建一个 TextRange 对象
var range = obj.createTextRange();
// 将范围折叠到起点
range.collapse(true);
// 将范围的结束位置移动到指定位置
range.moveEnd('character', pos);
// 将范围的起始位置移动到指定位置
range.moveStart('character', pos);
// 选择该范围
range.select();
}
},
showIfameMask: function (setting, showSign) {
// 获取根节点对象
var root = data.getRoot(setting);
// 清除所有遮罩层
while (root.dragMaskList.length > 0) {
// 移除第一个遮罩层元素
root.dragMaskList[0].remove();
// 从遮罩层列表中删除该元素
root.dragMaskList.shift();
}
// 如果需要显示遮罩层
if (showSign) {
// 获取所有的iframe元素
var iframeList = $$("iframe", setting);
// 遍历每一个iframe元素
for (var i = 0, l = iframeList.length; i < l; i++) {
// 获取当前iframe元素
var obj = iframeList.get(i),
// 获取iframe元素的绝对位置
r = tools.getAbs(obj),
// 创建遮罩层div并设置其样式和位置
dragMask = $$("<div id='zTreeMask_" + i + "' class='zTreeMask' style='top:" + r[1] + "px; left:" + r[0] + "px; width:" + obj.offsetWidth + "px; height:" + obj.offsetHeight + "px;'></div>", setting);
// 将遮罩层添加到body中
dragMask.appendTo($$("body", setting));
// 将遮罩层添加到根节点的遮罩层列表中
root.dragMaskList.push(dragMask);
}
}
}
},
//method of operate ztree dom
_view = {
// 添加编辑按钮的方法
addEditBtn: function (setting, node) {
// 如果节点已经有编辑名称标志或已经存在编辑按钮,则直接返回
if (node.editNameFlag || $$(node, consts.id.EDIT, setting).length > 0) {
return;
}
// 如果不允许显示重命名按钮,则直接返回
if (!tools.apply(setting.edit.showRenameBtn, [setting.treeId, node], setting.edit.showRenameBtn)) {
return;
}
// 获取节点的<a>元素
var aObj = $$(node, consts.id.A, setting),
// 创建编辑按钮的HTML字符串
editStr = "<span class='" + consts.className.BUTTON + " edit' id='" + node.tId + consts.id.EDIT + "' title='" + tools.apply(setting.edit.renameTitle, [setting.treeId, node], setting.edit.renameTitle) + "' treeNode" + consts.id.EDIT + " style='display:none;'></span>";
// 将编辑按钮添加到<a>元素中
aObj.append(editStr);
// 绑定点击事件到编辑按钮
$$(node, consts.id.EDIT, setting).bind('click',
function () {
// 如果用户不能进行操作或beforeEditName回调函数返回false则返回false
if (!tools.uCanDo(setting) || tools.apply(setting.callback.beforeEditName, [setting.treeId, node], true) == false) return false;
// 调用view.editNode方法进行编辑操作
view.editNode(setting, node);
return false;
}
// 显示编辑按钮
).show();
},
/**
* 添加或移除节点的按钮
* @param {Object} setting - 配置对象
* @param {Object} node - 当前节点对象
*/
addRemoveBtn: function (setting, node) {
// 如果节点正在编辑名称或者已经存在删除按钮,则直接返回
if (node.editNameFlag || $$(node, consts.id.REMOVE, setting).length > 0) {
return;
}
// 检查是否显示删除按钮,如果不允许显示则返回
if (!tools.apply(setting.edit.showRemoveBtn, [setting.treeId, node], setting.edit.showRemoveBtn)) {
return;
}
// 获取节点中的<a>元素
var aObj = $$(node, consts.id.A, setting),
// 创建删除按钮的HTML字符串
removeStr = "<span class='" + consts.className.BUTTON + " remove' id='" + node.tId + consts.id.REMOVE + "' title='" + tools.apply(setting.edit.removeTitle, [setting.treeId, node], setting.edit.removeTitle) + "' treeNode" + consts.id.REMOVE + " style='display:none;'></span>";
// 将删除按钮添加到<a>元素中
aObj.append(removeStr);
// 绑定点击事件到删除按钮
$$(node, consts.id.REMOVE, setting).bind('click',
function () {
// 检查是否可以执行删除操作如果不允许则返回false
if (!tools.uCanDo(setting) || tools.apply(setting.callback.beforeRemove, [setting.treeId, node], true) == false) return false;
// 调用view对象的removeNode方法删除节点
view.removeNode(setting, node);
// 触发删除事件
setting.treeObj.trigger(consts.event.REMOVE, [setting.treeId, node]);
return false;
}
).bind('mousedown',
function (eventMouseDown) {
// 阻止默认行为并返回true
return true;
}
).show(); // 显示删除按钮
},
addHoverDom: function (setting, node) {
// 检查是否显示悬停DOM
if (data.getRoots().showHoverDom) {
// 设置节点为悬停状态
node.isHover = true;
// 如果编辑功能启用,添加编辑和删除按钮
if (setting.edit.enable) {
view.addEditBtn(setting, node);
view.addRemoveBtn(setting, node);
}
// 调用自定义的悬停DOM添加方法
tools.apply(setting.view.addHoverDom, [setting.treeId, node]);
}
},
cancelCurEditNode: function (setting, forceName, isCancel) {
// 获取当前根节点
var root = data.getRoot(setting),
// 获取当前正在编辑的节点
node = root.curEditNode;
if (node) {
// 获取当前编辑的输入对象
var inputObj = root.curEditInput,
// 如果强制名称存在,则使用它;否则根据是否取消操作决定新名称
newName = forceName ? forceName : (isCancel ? data.nodeName(setting, node) : inputObj.val());
// 调用beforeRename回调函数如果返回false则终止重命名操作
if (tools.apply(setting.callback.beforeRename, [setting.treeId, node, newName, isCancel], true) === false) {
return false;
}
// 更新节点的名称
data.nodeName(setting, node, newName);
// 获取节点对应的<a>元素并移除选中样式
var aObj = $$(node, consts.id.A, setting);
aObj.removeClass(consts.node.CURSELECTED_EDIT);
// 解除输入对象的绑定事件
inputObj.unbind();
// 更新视图中的节点名称
view.setNodeName(setting, node);
// 重置节点的编辑状态标志
node.editNameFlag = false;
// 清空当前编辑的节点和输入对象
root.curEditNode = null;
root.curEditInput = null;
// 取消节点的选择状态
view.selectNode(setting, node, false);
// 触发重命名事件
setting.treeObj.trigger(consts.event.RENAME, [setting.treeId, node, isCancel]);
}
// 设置无选择状态
root.noSelection = true;
// 返回true表示操作成功
return true;
},
/**
* 编辑节点名称
* @param {Object} setting - 配置对象
* @param {Object} node - 要编辑的节点对象
*/
editNode: function (setting, node) {
// 获取根节点数据
var root = data.getRoot(setting);
// 标记编辑节点时失去焦点的状态为false
view.editNodeBlur = false;
// 如果节点已被选中且当前正在编辑该节点,则设置输入框焦点
if (data.isSelectedNode(setting, node) && root.curEditNode == node && node.editNameFlag) {
setTimeout(function () {
tools.inputFocus(root.curEditInput);
}, 0);
return;
}
// 设置节点的编辑名称标志为true
node.editNameFlag = true;
// 从视图中移除树形DOM元素
view.removeTreeDom(setting, node);
// 取消当前正在编辑的节点
view.cancelCurEditNode(setting);
// 选择指定的节点,不触发事件
view.selectNode(setting, node, false);
// 在指定节点的SPAN元素中插入一个输入框用于重命名节点
$$(node, consts.id.SPAN, setting).html("<input type=text class='rename' id='" + node.tId + consts.id.INPUT + "' treeNode" + consts.id.INPUT + " >");
// 获取新插入的输入框对象
var inputObj = $$(node, consts.id.INPUT, setting);
// 设置输入框的值为当前节点的名称
inputObj.attr("value", data.nodeName(setting, node));
// 如果配置允许全选输入框内容,则全选输入框内容;否则,将焦点设置到输入框
if (setting.edit.editNameSelectAll) {
tools.inputSelect(inputObj);
} else {
tools.inputFocus(inputObj);
}
inputObj.bind('blur', function (event) {
// 当输入框失去焦点时触发
if (!view.editNodeBlur) {
// 如果当前没有编辑节点处于模糊状态,则取消当前编辑节点
view.cancelCurEditNode(setting);
}
}).bind('keydown', function (event) {
// 当在输入框中按下键盘按键时触发
if (event.keyCode == "13") {
// 如果按下的是回车键keyCode为13
view.editNodeBlur = true;
// 设置编辑节点为模糊状态
view.cancelCurEditNode(setting);
// 取消当前编辑节点
} else if (event.keyCode == "27") {
// 如果按下的是Esc键keyCode为27
view.cancelCurEditNode(setting, null, true);
// 取消当前编辑节点并传递额外参数
}
}).bind('click', function (event) {
// 当点击输入框时触发
return false;
// 阻止默认的点击事件行为
}).bind('dblclick', function (event) {
// 当双击输入框时触发
return false;
// 阻止默认的双击事件行为
});
$$(node, consts.id.A, setting).addClass(consts.node.CURSELECTED_EDIT);
// 为当前编辑的节点添加选中样式类
root.curEditInput = inputObj;
// 设置当前正在编辑的输入对象
root.noSelection = false;
// 标记当前有选择的节点
root.curEditNode = node;
// 记录当前正在编辑的节点
},
moveNode: function (setting, targetNode, node, moveType, animateFlag, isSilent) {
var root = data.getRoot(setting);
// 获取根节点
if (targetNode == node) return;
// 如果目标节点和当前节点相同,则不进行任何操作
if (setting.data.keep.leaf && targetNode && !data.nodeIsParent(setting, targetNode) && moveType == consts.move.TYPE_INNER) return;
// 如果配置中要求保持叶子节点,并且目标节点不是父节点且移动类型是内部移动,则不进行任何操作
var oldParentNode = (node.parentTId ? node.getParentNode() : root),
// 获取当前节点的父节点,如果没有父节点则为根节点
targetNodeIsRoot = (targetNode === null || targetNode == root);
// 判断目标节点是否是根节点
if (targetNodeIsRoot && targetNode === null) targetNode = root;
// 如果目标节点为空,则将其设置为根节点
if (targetNodeIsRoot) moveType = consts.move.TYPE_INNER;
// 如果目标节点是根节点,则将移动类型设置为内部移动
var targetParentNode = (targetNode.parentTId ? targetNode.getParentNode() : root);
// 获取目标节点的父节点,如果没有父节点则为根节点
if (moveType != consts.move.TYPE_PREV && moveType != consts.move.TYPE_NEXT) {
moveType = consts.move.TYPE_INNER;
// 如果移动类型既不是前一个也不是后一个,则默认设置为内部移动
}
if (moveType == consts.move.TYPE_INNER) {
// 检查目标节点是否是根节点
if (targetNodeIsRoot) {
// 如果是根节点设置父节点ID为null
node.parentTId = null;
} else {
// 如果目标节点不是父节点
if (!data.nodeIsParent(setting, targetNode)) {
// 将目标节点设置为父节点
data.nodeIsParent(setting, targetNode, true);
// 切换目标节点的打开状态
targetNode.open = !!targetNode.open;
// 更新视图中的节点图标和线条
view.setNodeLineIcos(setting, targetNode);
}
// 设置当前节点的父节点ID为目标节点的ID
node.parentTId = targetNode.tId;
}
}
//move node Dom
var targetObj, target_ulObj;
// 判断目标节点是否是根节点
if (targetNodeIsRoot) {
// 如果是根节点则目标对象和目标的UL对象都指向树对象
targetObj = setting.treeObj;
target_ulObj = targetObj;
} else {
// 如果目标节点不是根节点
if (!isSilent && moveType == consts.move.TYPE_INNER) {
// 如果不是静默模式且移动类型为内部移动,则展开目标节点
view.expandCollapseNode(setting, targetNode, true, false);
} else if (!isSilent) {
// 如果不是静默模式,则展开目标节点的父节点
view.expandCollapseNode(setting, targetNode.getParentNode(), true, false);
}
// 获取目标节点对应的DOM对象
targetObj = $$(targetNode, setting);
// 获取目标节点对应的UL DOM对象
target_ulObj = $$(targetNode, consts.id.UL, setting);
// 如果目标对象存在但UL对象不存在
if (!!targetObj.get(0) && !target_ulObj.get(0)) {
// 创建一个空数组用于存储UL HTML字符串
var ulstr = [];
// 生成UL HTML并添加到数组中
view.makeUlHtml(setting, targetNode, ulstr, '');
// 将生成的UL HTML追加到目标对象中
targetObj.append(ulstr.join(''));
}
// 再次获取目标节点对应的UL DOM对象
target_ulObj = $$(targetNode, consts.id.UL, setting);
}
// 获取节点的DOM对象
var nodeDom = $$(node, setting);
// 如果节点的DOM对象不存在
if (!nodeDom.get(0)) {
// 将节点添加到视图中并生成对应的HTML字符串
nodeDom = view.appendNodes(setting, node.level, [node], null, -1, false, true).join('');
} else if (!targetObj.get(0)) {
// 如果目标对象不存在则移除节点的DOM对象
nodeDom.remove();
}
// 如果目标UL对象存在且移动类型为内部移动
if (target_ulObj.get(0) && moveType == consts.move.TYPE_INNER) {
// 将节点的DOM对象追加到目标UL对象中
target_ulObj.append(nodeDom);
} else if (targetObj.get(0) && moveType == consts.move.TYPE_PREV) {
// 如果目标对象存在且移动类型为前置移动将节点的DOM对象插入到目标对象之前
targetObj.before(nodeDom);
} else if (targetObj.get(0) && moveType == consts.move.TYPE_NEXT) {
// 如果目标对象存在且移动类型为后置移动将节点的DOM对象插入到目标对象之后
targetObj.after(nodeDom);
}
//repair the data after move
var i, l,
// 初始化源索引为-1表示未找到
tmpSrcIndex = -1,
// 初始化目标索引为0
tmpTargetIndex = 0,
// 初始化旧的邻居节点为null
oldNeighbor = null,
// 初始化新的邻居节点为null
newNeighbor = null,
// 获取当前节点的层级
oldLevel = node.level;
// 获取旧父节点的所有子节点
var oldChildren = data.nodeChildren(setting, oldParentNode);
// 获取目标父节点的所有子节点
var targetParentChildren = data.nodeChildren(setting, targetParentNode);
// 获取目标节点的所有子节点
var targetChildren = data.nodeChildren(setting, targetNode);
// 如果当前节点是第一个节点
if (node.isFirstNode) {
// 设置源索引为0
tmpSrcIndex = 0;
// 如果旧父节点有多个子节点
if (oldChildren.length > 1) {
// 将第二个子节点设为旧的邻居节点
oldNeighbor = oldChildren[1];
// 标记该邻居节点为第一个节点
oldNeighbor.isFirstNode = true;
}
// 如果当前节点是最后一个节点
} else if (node.isLastNode) {
// 设置源索引为最后一个子节点的索引
tmpSrcIndex = oldChildren.length - 1;
// 将倒数第二个子节点设为旧的邻居节点
oldNeighbor = oldChildren[tmpSrcIndex - 1];
// 标记该邻居节点为最后一个节点
oldNeighbor.isLastNode = true;
// 如果当前节点既不是第一个也不是最后一个节点
} else {
// 遍历旧父节点的所有子节点
for (i = 0, l = oldChildren.length; i < l; i++) {
// 如果找到与当前节点ID相同的子节点
if (oldChildren[i].tId == node.tId) {
// 设置源索引为该子节点的索引
tmpSrcIndex = i;
// 退出循环
break;
}
}
}
if (tmpSrcIndex >= 0) {
// 如果源节点索引有效,从旧子节点数组中移除该节点
oldChildren.splice(tmpSrcIndex, 1);
}
if (moveType != consts.move.TYPE_INNER) {
// 如果移动类型不是内部移动,找到目标节点在父节点中的索引
for (i = 0, l = targetParentChildren.length; i < l; i++) {
if (targetParentChildren[i].tId == targetNode.tId) tmpTargetIndex = i;
}
}
if (moveType == consts.move.TYPE_INNER) {
// 如果移动类型是内部移动
if (!targetChildren) {
// 如果目标节点没有子节点,初始化目标节点的子节点数组
targetChildren = data.nodeChildren(setting, targetNode, []);
}
if (targetChildren.length > 0) {
// 如果目标节点有子节点设置最后一个子节点的isLastNode属性为false
newNeighbor = targetChildren[targetChildren.length - 1];
newNeighbor.isLastNode = false;
}
// 将节点插入到目标节点的子节点数组末尾
targetChildren.splice(targetChildren.length, 0, node);
// 设置新插入节点的isLastNode属性为true
node.isLastNode = true;
// 设置新插入节点的isFirstNode属性为true如果这是第一个子节点
node.isFirstNode = (targetChildren.length == 1);
} else if (targetNode.isFirstNode && moveType == consts.move.TYPE_PREV) {
// 如果目标节点是第一个节点且移动类型是前移,将节点插入到目标节点之前
targetParentChildren.splice(tmpTargetIndex, 0, node);
// 更新新邻居节点为原目标节点
newNeighbor = targetNode;
// 设置原目标节点的isFirstNode属性为false
newNeighbor.isFirstNode = false;
// 设置新插入节点的父节点ID为目标节点的父节点ID
node.parentTId = targetNode.parentTId;
// 设置新插入节点的isFirstNode属性为true
node.isFirstNode = true;
// 设置新插入节点的isLastNode属性为false
node.isLastNode = false;
else if (targetNode.isLastNode && moveType == consts.move.TYPE_NEXT) {
// 如果目标节点是最后一个节点并且移动类型为“下一个”
targetParentChildren.splice(tmpTargetIndex + 1, 0, node);
// 在目标节点的父节点子节点数组中,将当前节点插入到目标节点之后的位置
newNeighbor = targetNode;
// 设置新的相邻节点为目标节点
newNeighbor.isLastNode = false;
// 更新新相邻节点的isLastNode属性为false
node.parentTId = targetNode.parentTId;
// 将当前节点的父节点ID设置为目标节点的父节点ID
node.isFirstNode = false;
// 设置当前节点的isFirstNode属性为false
node.isLastNode = true;
// 设置当前节点的isLastNode属性为true
// 更新新相邻节点的isLastNode属性为false
node.parentTId = targetNode.parentTId;
// 将当前节点的父节点ID设置为目标节点的父节点ID
node.isFirstNode = false;
// 设置当前节点的isFirstNode属性为false
node.isLastNode = true;
// 设置当前节点的isLastNode属性为true
// 更新新相邻节点的isLastNode属性为false
node.parentTId = targetNode.parentTId;
// 将当前节点的父节点ID设置为目标节点的父节点ID
node.isFirstNode = false;
// 设置当前节点的isFirstNode属性为false
node.isLastNode = true;
// 设置当前节点的isLastNode属性为true
// 更新新相邻节点的isLastNode属性为false
node.parentTId = targetNode.parentTId;
// 将当前节点的父节点ID设置为目标节点的父节点ID
node.isFirstNode = false;
// 设置当前节点的isFirstNode属性为false
node.isLastNode = true;
// 设置当前节点的isLastNode属性为true
// 更新新相邻节点的isLastNode属性为false
node.parentTId = targetNode.parentTId;
// 将当前节点的父节点ID设置为目标节点的父节点ID
node.isFirstNode = false;
// 设置当前节点的isFirstNode属性为false
node.isLastNode = true;
// 设置当前节点的isLastNode属性为true
// 更新新相邻节点的isLastNode属性为false
node.parentTId = targetNode.parentTId;
// 将当前节点的父节点ID设置为目标节点的父节点ID
node.isFirstNode = false;
// 设置当前节点的isFirstNode属性为false
node.isLastNode = true;
// 设置当前节点的isLastNode属性为true
// 更新新相邻节点的isLastNode属性为false
node.parentTId = targetNode.parentTId;
// 将当前节点的父节点ID设置为目标节点的父节点ID
node.isFirstNode = false;
// 设置当前节点的isFirstNode属性为false
node.isLastNode = true;
// 设置当前节点的isLastNode属性为true
// 更新新相邻节点的isLastNode属性为false
node.parentTId = targetNode.parentTId;
// 将当前节点的父节点ID设置为目标节点的父节点ID
node.isFirstNode = false;
// 设置当前节点的isFirstNode属性为false
node.isLastNode = true;
// 设置当前节点的isLastNode属性为true
// 更新新相邻节点的isLastNode属性为false
node.parentTId = targetNode.parentTId;
// 将当前节点的父节点ID设置为目标节点的父节点ID
node.isFirstNode = false;
// 设置当前节点的isFirstNode属性为false
node.isLastNode = true;
// 设置当前节点的isLastNode属性为true
// 更新新相邻节点的isLastNode属性为false
node.parentTId = targetNode.parentTId;
// 将当前节点的父节点ID设置为目标节点的父节点ID
node.isFirstNode = false;
// 设置当前节点的isFirstNode属性为false
node.isLastNode = true;
// 设置当前节点的isLastNode属性为true
else {
// 判断移动类型是否为前移
if (moveType == consts.move.TYPE_PREV) {
// 将节点插入到目标父节点的子节点数组中的指定位置
targetParentChildren.splice(tmpTargetIndex, 0, node);
} else {
// 将节点插入到目标父节点的子节点数组中的下一个位置
targetParentChildren.splice(tmpTargetIndex + 1, 0, node);
}
// 更新节点的父节点ID
node.parentTId = targetNode.parentTId;
// 设置节点不是第一个节点
node.isFirstNode = false;
// 设置节点不是最后一个节点
node.isLastNode = false;
}
// 修复节点的父节点ID键值对
data.fixPIdKeyValue(setting, node);
// 设置节点的子节点级别
data.setSonNodeLevel(setting, node.getParentNode(), node);
// 修复被移动的节点
view.setNodeLineIcos(setting, node);
// 修复节点的旧级别样式类
view.repairNodeLevelClass(setting, node, oldLevel);
// 修复节点的旧父节点DOM
if (!setting.data.keep.parent && oldChildren.length < 1) {
// 如果旧父节点没有子节点
// 设置旧父节点不再是父节点
data.nodeIsParent(setting, oldParentNode, false);
// 关闭旧父节点
oldParentNode.open = false;
var tmp_ulObj = $$(oldParentNode, consts.id.UL, setting),
tmp_switchObj = $$(oldParentNode, consts.id.SWITCH, setting),
tmp_icoObj = $$(oldParentNode, consts.id.ICON, setting);
// 替换旧父节点的开关类
view.replaceSwitchClass(oldParentNode, tmp_switchObj, consts.folder.DOCU);
// 替换旧父节点的图标类
view.replaceIcoClass(oldParentNode, tmp_icoObj, consts.folder.DOCU);
// 隐藏旧父节点的子节点列表
// 将临时的ul对象隐藏设置其CSS属性display为none
tmp_ulObj.css("display", "none");
else if (oldNeighbor) {
// 如果存在旧的邻居节点
view.setNodeLineIcos(setting, oldNeighbor);
// 调用view对象的setNodeLineIcos方法将设置和旧的邻居节点作为参数传递
}
// 如果存在新的邻居节点
if (newNeighbor) {
// 设置新邻居节点的线条样式
view.setNodeLineIcos(setting, newNeighbor);
}
// 修复复选框/单选按钮的状态
if (!!setting.check && setting.check.enable && view.repairChkClass) {
// 修复旧父节点的复选框类
view.repairChkClass(setting, oldParentNode);
// 修复旧父节点及其子节点的复选框类
view.repairParentChkClassWithSelf(setting, oldParentNode);
// 如果旧父节点与当前节点的父节点不同,则修复当前节点的复选框类
if (oldParentNode != node.parent)
view.repairParentChkClassWithSelf(setting, node);
}
//expand parents after move
if (!isSilent) {
// 如果isSilent为false展开或折叠父节点
view.expandCollapseParentNode(setting, node.getParentNode(), true, animateFlag);
}
},
removeEditBtn: function (setting, node) {
// 解绑并移除编辑按钮
$$(node, consts.id.EDIT, setting).unbind().remove();
},
removeRemoveBtn: function (setting, node) {
// 解绑并移除删除按钮
$$(node, consts.id.REMOVE, setting).unbind().remove();
},
removeTreeDom: function (setting, node) {
// 设置节点的hover状态为false
node.isHover = false;
// 移除编辑按钮
view.removeEditBtn(setting, node);
// 移除删除按钮
view.removeRemoveBtn(setting, node);
// 调用工具方法移除hover效果的DOM元素
tools.apply(setting.view.removeHoverDom, [setting.treeId, node]);
},
repairNodeLevelClass: function (setting, node, oldLevel) {
// 如果旧级别与当前级别相同,则无需修复
// 如果旧的层级与当前节点的层级相同,则直接返回,不做任何操作
if (oldLevel === node.level) return;
// 获取当前节点对应的li元素对象
var liObj = $$(node, setting),
// 获取当前节点对应的a元素对象
aObj = $$(node, consts.id.A, setting),
// 获取当前节点对应的ul元素对象
ulObj = $$(node, consts.id.UL, setting),
// 定义旧的层级类名
oldClass = consts.className.LEVEL + oldLevel,
// 定义新的层级类名
newClass = consts.className.LEVEL + node.level;
// 移除旧的级别类名并添加新的级别类名
// 从li元素中移除旧的CSS类
liObj.removeClass(oldClass);
// 向li元素添加新的CSS类
liObj.addClass(newClass);
// 从a元素中移除旧的CSS类
aObj.removeClass(oldClass);
// 向a元素添加新的CSS类
aObj.addClass(newClass);
// 从ul元素中移除旧的CSS类
ulObj.removeClass(oldClass);
// 向ul元素添加新的CSS类
ulObj.addClass(newClass);
},
selectNodes: function (setting, nodes) {
// 遍历所有节点并选择它们
// 遍历节点数组,从第一个节点开始到最后一个节点结束
for (var i = 0, l = nodes.length; i < l; i++) {
// 调用view对象的selectNode方法选择当前节点
// 参数1setting对象包含配置信息
// 参数2当前节点nodes[i]
// 参数3布尔值表示是否为非第一个节点i > 0
view.selectNode(setting, nodes[i], i > 0);
}
}
}
},
_z = {
// 将工具对象赋值给_z对象的tools属性
tools: _tools,
// 将视图对象赋值给_z对象的view属性
view: _view,
// 将事件对象赋值给_z对象的event属性
event: _event,
// 将数据对象赋值给_z对象的data属性
data: _data
};
// 将_consts对象的属性深度复制到$.fn.zTree.consts对象中
$.extend(true, $.fn.zTree.consts, _consts);
// 将_z对象的属性深度复制到$.fn.zTree._z对象中
$.extend(true, $.fn.zTree._z, _z);
// 获取zTree插件的命名空间
var zt = $.fn.zTree,
// 获取工具对象
tools = zt._z.tools,
// 获取常量对象
consts = zt.consts,
// 获取视图对象
view = zt._z.view,
// 获取数据对象
data = zt._z.data,
// 获取事件对象
event = zt._z.event,
// 获取工具中的$函数用于简化DOM操作
$$ = tools.$;
// 调用exSetting方法传入_setting参数进行设置
data.exSetting(_setting);
// 添加初始化绑定事件的方法传入_bindEvent参数
data.addInitBind(_bindEvent);
// 添加初始化解绑事件的方法传入_unbindEvent参数
data.addInitUnBind(_unbindEvent);
// 添加初始化缓存的方法传入_initCache参数
data.addInitCache(_initCache);
// 添加初始化节点的方法传入_initNode参数
data.addInitNode(_initNode);
// 添加初始化代理事件的方法传入_eventProxy参数
data.addInitProxy(_eventProxy);
// 添加初始化根节点的方法传入_initRoot参数
data.addInitRoot(_initRoot);
// 添加ZTree工具的方法传入_zTreeTools参数
data.addZTreeTools(_zTreeTools);
var _cancelPreSelectedNode = view.cancelPreSelectedNode; // 保存原始的取消预选节点函数
view.cancelPreSelectedNode = function (setting, node) { // 重写取消预选节点函数
var list = data.getRoot(setting).curSelectedList; // 获取当前选中的节点列表
for (var i = 0, j = list.length; i < j; i++) { // 遍历当前选中的节点列表
if (!node || node === list[i]) { // 如果未指定节点或当前节点是需要取消的节点
view.removeTreeDom(setting, list[i]); // 移除该节点对应的DOM元素
if (node) break; // 如果指定了节点,则中断循环
}
}
if (_cancelPreSelectedNode) _cancelPreSelectedNode.apply(view, arguments); // 调用原始的取消预选节点函数
}
var _createNodes = view.createNodes;
view.createNodes = function (setting, level, nodes, parentNode, index) {
// 如果存在原始的 createNodes 方法,则调用它
if (_createNodes) {
_createNodes.apply(view, arguments);
}
// 如果没有节点需要创建,直接返回
if (!nodes) return;
// 如果存在 repairParentChkClassWithSelf 方法,则调用它修复父节点的复选框类
if (view.repairParentChkClassWithSelf) {
view.repairParentChkClassWithSelf(setting, parentNode);
}
}
var _makeNodeUrl = view.makeNodeUrl;
view.makeNodeUrl = function (setting, node) {
// 如果编辑功能启用,则返回 null否则调用原始的 makeNodeUrl 方法
return setting.edit.enable ? null : (_makeNodeUrl.apply(view, arguments));
}
var _removeNode = view.removeNode;
view.removeNode = function (setting, node) {
// 获取当前根节点
var root = data.getRoot(setting);
// 如果当前正在编辑的节点是要删除的节点,则将当前编辑节点设为 null
if (root.curEditNode === node) root.curEditNode = null;
// 如果存在原始的 removeNode 方法,则调用它
if (_removeNode) {
_removeNode.apply(view, arguments);
}
}
var _selectNode = view.selectNode;
view.selectNode = function (setting, node, addFlag) {
// 获取根节点
var root = data.getRoot(setting);
// 如果当前节点已被选中且正在编辑则返回false
if (data.isSelectedNode(setting, node) && root.curEditNode == node && node.editNameFlag) {
return false;
}
// 调用原始的selectNode方法
if (_selectNode) _selectNode.apply(view, arguments);
// 添加悬停DOM元素
view.addHoverDom(setting, node);
return true;
}
var _uCanDo = tools.uCanDo;
tools.uCanDo = function (setting, e) {
// 获取根节点
var root = data.getRoot(setting);
// 如果事件类型是鼠标相关事件则返回true
if (e && (tools.eqs(e.type, "mouseover") || tools.eqs(e.type, "mouseout") || tools.eqs(e.type, "mousedown") || tools.eqs(e.type, "mouseup"))) {
return true;
}
// 如果当前有正在编辑的节点,则取消其模糊状态并聚焦输入框
if (root.curEditNode) {
view.editNodeBlur = false;
root.curEditInput.focus();
}
// 如果没有正在编辑的节点则调用原始的uCanDo方法或返回true
return (!root.curEditNode) && (_uCanDo ? _uCanDo.apply(view, arguments) : true);
}
})(jQuery);
// 加载zTree样式表
layui.link(layui.cache.base+'ztree/zTreeStyle.css')
// 导出zTree模块
exports("ztree",jQuery.fn.zTree)
});