import {
cjkEncodeDO,
Controller,
createWidget,
emptyFn,
Events,
extend,
UUID,
isNotNull,
jsonEncode,
delay,
each,
replaceAll,
isUndefined,
isNotEmptyArray,
deepClone,
map,
Tree,
isNull,
shortcut,
VerticalLayout,
Layout,
DefaultLayout,
some,
Widget,
STYLE_CONSTANTS
} from "@/core";
import { Msg, Pane, LoadingBar, Text } from "@/base";
import $ from "jquery";
@shortcut()
export class TreeView extends Pane {
static xtype = "bi.tree_view";
static REQ_TYPE_INIT_DATA = 1;
static REQ_TYPE_ADJUST_DATA = 2;
static REQ_TYPE_SELECT_DATA = 3;
static REQ_TYPE_GET_SELECTED_DATA = 4;
static EVENT_CHANGE = "EVENT_CHANGE";
static EVENT_INIT = Events.INIT;
static EVENT_AFTERINIT = Events.AFTERINIT;
_defaultConfig() {
return extend(super._defaultConfig(...arguments), {
_baseCls: "bi-tree",
paras: {
selectedValues: {},
},
itemsCreator: emptyFn,
showLine: true,
});
}
_init() {
super._init(...arguments);
const o = this.options;
this._stop = false;
this._createTree();
this.tip = createWidget({
type: LoadingBar.xtype,
invisible: true,
handler: () => this._loadMore(),
});
createWidget({
type: VerticalLayout.xtype,
scrollable: true,
scrolly: false,
element: this,
items: [this.tip],
});
if (isNotNull(o.value)) {
this.setSelectedValue(o.value);
}
}
_createTree() {
this.id = `bi-tree${UUID()}`;
if (this.nodes) {
this.nodes.destroy();
}
if (this.tree) {
this.tree.destroy();
}
this.tree = createWidget({
type: Layout.xtype,
element: `
`,
});
createWidget({
type: DefaultLayout.xtype,
element: this,
items: [this.tree],
});
}
// 选择节点触发方法
_selectTreeNode(treeId, treeNode) {
this.fireEvent(Controller.EVENT_CHANGE, Events.CLICK, treeNode, this);
this.fireEvent(TreeView.EVENT_CHANGE, treeNode, this);
}
// 配置属性
_configSetting() {
const paras = this.options.paras;
const self = this;
const o = this.options;
const setting = {
async: {
enable: true,
url: getUrl,
autoParam: ["id", "name"], // 节点展开异步请求自动提交id和name
otherParam: cjkEncodeDO(paras), // 静态参数
},
check: {
enable: true,
},
data: {
key: {
title: "title",
name: "text", // 节点的name属性替换成text
},
simpleData: {
enable: true, // 可以穿id,pid属性的对象数组
},
},
view: {
showIcon: false,
expandSpeed: "",
nameIsHTML: true, // 节点可以用html标签代替
dblClickExpand: false,
showLine: o.showLine,
},
callback: {
beforeExpand,
onAsyncSuccess,
onAsyncError,
beforeCheck,
onCheck,
onExpand,
onCollapse,
onClick,
},
};
const className = "dark",
perTime = 100;
function onClick(event, treeId, treeNode) {
// 当前点击节点的状态是半选,且为true_part, 则将其改为false_part,使得点击半选后切换到的是全选
let checked = treeNode.checked;
const status = treeNode.getCheckStatus();
if (status.half === true && status.checked === true) {
checked = false;
}
// 更新此node的check状态, 影响父子关联,并调用beforeCheck和onCheck回调
self.nodes.checkNode(treeNode, !checked, true, true);
}
function getUrl(treeId, treeNode) {
const parentNode = self._getParentValues(treeNode);
treeNode.times = treeNode.times || 1;
const param = `id=${treeNode.id}×=${treeNode.times++}&parentValues= ${_global.encodeURIComponent(
jsonEncode(parentNode)
)}&checkState=${_global.encodeURIComponent(jsonEncode(treeNode.getCheckStatus()))}`;
return `&${param}`;
}
function beforeExpand(treeId, treeNode) {
if (!treeNode.isAjaxing) {
if (!treeNode.children) {
treeNode.times = 1;
ajaxGetNodes(treeNode, "refresh");
}
return true;
}
Msg.toast("Please Wait。", {
level: "warning",
}); // 不展开节点,也不触发onExpand事件
return false;
}
function onAsyncSuccess(event, treeId, treeNode, msg) {
treeNode.halfCheck = false;
if (!msg || msg.length === 0 || /^[\s,\S]*<\/html>$/gi.test(msg) || self._stop) {
return;
}
const zTree = self.nodes;
const totalCount = treeNode.count || 0;
// 尝试去获取下一组节点,若获取值为空数组,表示获取完成
// TODO by GUY
if (treeNode.children.length > totalCount) {
treeNode.count = treeNode.children.length;
delay(() => {
ajaxGetNodes(treeNode);
}, perTime);
} else {
// treeNode.icon = "";
zTree.updateNode(treeNode);
zTree.selectNode(treeNode.children[0]);
// className = (className === "dark" ? "":"dark");
}
}
function onAsyncError(event, treeId, treeNode, XMLHttpRequest, textStatus, errorThrown) {
const zTree = self.nodes;
Msg.toast("Error!", "warning");
// treeNode.icon = "";
// zTree.updateNode(treeNode);
}
function ajaxGetNodes(treeNode, reloadType) {
const zTree = self.nodes;
if (reloadType === "refresh") {
zTree.updateNode(treeNode); // 刷新一下当前节点,如果treeNode.xxx被改了的话
}
zTree.reAsyncChildNodes(treeNode, reloadType, true); // 强制加载子节点,reloadType === refresh为先清空再加载,否则为追加到现有子节点之后
}
function beforeCheck(treeId, treeNode) {
if (treeNode.disabled) {
return false;
}
// 下面主动修改了node的halfCheck属性, 节点属性的判断依赖halfCheck,改之前就获取一下
const status = treeNode.getCheckStatus();
treeNode.halfCheck = false;
if (treeNode.checked === true) {
// 将展开的节点halfCheck设为false,解决展开节点存在halfCheck=true的情况 guy
// 所有的半选状态都需要取消halfCheck=true的情况
function track(children) {
each(children, (i, ch) => {
if (ch.halfCheck === true) {
ch.halfCheck = false;
track(ch.children);
}
});
}
track(treeNode.children);
const treeObj = self.nodes;
const nodes = treeObj.getSelectedNodes();
$.each(nodes, (index, node) => {
node.halfCheck = false;
});
}
// 当前点击节点的状态是半选,且为true_part, 则将其改为false_part,使得点击半选后切换到的是全选
if (status.half === true && status.checked === true) {
treeNode.checked = false;
}
}
function onCheck(event, treeId, treeNode) {
if (treeNode.disabled) {
return false;
}
self._selectTreeNode(treeId, treeNode);
}
function onExpand(event, treeId, treeNode) {
treeNode.halfCheck = false;
}
function onCollapse(event, treeId, treeNode) {}
return setting;
}
_getParentValues(treeNode) {
if (!treeNode.getParentNode()) {
return [];
}
const parentNode = treeNode.getParentNode();
let result = this._getParentValues(parentNode);
result = result.concat([this._getNodeValue(parentNode)]);
return result;
}
_getNodeValue(node) {
// 去除标红
return isUndefined(node.value) ? replaceAll(node.text.replace(/<[^>]+>/g, ""), " ", " ") : node.value;
}
// 获取半选框值
_getHalfSelectedValues(map, node) {
const self = this;
const checkState = node.getCheckStatus();
// 将未选的去掉
if (checkState.checked === false && checkState.half === false) {
return;
}
// 如果节点已展开,并且是半选
if (isNotEmptyArray(node.children) && checkState.half === true) {
const children = node.children;
each(children, (i, ch) => {
self._getHalfSelectedValues(map, ch);
});
return;
}
const parent = node.parentValues || self._getParentValues(node);
const path = parent.concat(this._getNodeValue(node));
// 当前节点是全选的,因为上面的判断已经排除了不选和半选
if (isNotEmptyArray(node.children) || checkState.half === false) {
this._buildTree(map, path);
return;
}
// 剩下的就是半选不展开的节点,因为不知道里面是什么情况,所以借助selectedValues(这个是完整的选中情况)
const storeValues = deepClone(this.options.paras.selectedValues);
const treeNode = this._getTree(storeValues, path);
this._addTreeNode(map, parent, this._getNodeValue(node), treeNode);
}
// 获取的是以values最后一个节点为根的子树
_getTree(map, values) {
let cur = map;
some(values, (i, value) => {
if (cur[value] == null) {
return true;
}
cur = cur[value];
});
return cur;
}
// 以values为path一路向里补充map, 并在末尾节点添加key: value节点
_addTreeNode(map, values, key, value) {
let cur = map;
each(values, (i, value) => {
if (cur[value] == null) {
cur[value] = {};
}
cur = cur[value];
});
cur[key] = value;
}
// 构造树节点
_buildTree(map, values) {
let cur = map;
each(values, (i, value) => {
if (cur[value] == null) {
cur[value] = {};
}
cur = cur[value];
});
}
// 获取选中的值
_getSelectedValues() {
const self = this;
const hashMap = {};
const rootNoots = this.nodes.getNodes();
track(rootNoots); // 可以看到这个方法没有递归调用,所以在_getHalfSelectedValues中需要关心全选的节点
function track(nodes) {
each(nodes, (i, node) => {
const checkState = node.getCheckStatus();
if (checkState.checked === true || checkState.half === true) {
if (checkState.half === true) {
self._getHalfSelectedValues(hashMap, node);
} else {
const parentValues = node.parentValues || self._getParentValues(node);
const values = parentValues.concat([self._getNodeValue(node)]);
self._buildTree(hashMap, values);
}
}
});
}
return hashMap;
}
// 处理节点
_dealWidthNodes(nodes) {
const self = this,
o = this.options;
const ns = Tree.arrayFormat(nodes);
return map(ns, (i, n) => {
const newNode = extend({}, n);
newNode.isParent = newNode.isParent || newNode.parent;
// n.value = BI.isUndefined(n.value) ? n.text : n.value;
// n.text = BI.isUndefined(n.text) ? n.value : n.text;
// if (n.text === null) {
// n.text = "";
// }
if (isNull(newNode.title)) {
newNode.title = newNode.text;
}
if (newNode.disabled) {
newNode.title = newNode.warningTitle || newNode.title;
}
const text = createWidget(
extend(
{
cls: "tree-node-text",
tagName: "span",
whiteSpace: "nowrap",
root: true,
keyword: o.paras.keyword,
},
newNode,
{
type: Text.xtype,
text: replaceAll(newNode.text, "\n", " "),
}
)
);
const fragment = Widget._renderEngine.createElement("");
fragment.append(text.element[0]);
newNode.text = fragment.html();
// // 处理标红
// if (BI.isNotNull(n.text)) {
// if (BI.isKey(o.paras.keyword)) {
// n.text = $("
").__textKeywordMarked__(BI.Text.formatText(n.text + ""), o.paras.keyword, n.py).html();
// } else {
// n.text = BI.htmlEncode(BI.Text.formatText(n.text + ""));
// }
// }
return newNode;
});
}
_loadMore() {
const self = this,
o = this.options;
this.tip.setLoading();
const op = extend({}, o.paras, {
times: ++this.times,
});
o.itemsCreator(op, res => {
if (self._stop === true) {
return;
}
const hasNext = !!res.hasNext,
nodes = res.items || [];
if (!hasNext) {
self.tip.setEnd();
} else {
self.tip.setLoaded();
}
if (nodes.length > 0) {
self.nodes.addNodes(null, self._dealWidthNodes(nodes));
}
});
}
// 生成树内部方法
_initTree(setting) {
const self = this,
o = this.options;
self.fireEvent(Events.INIT);
this.times = 1;
const tree = this.tree;
tree.empty();
this.loading();
this.tip.setVisible(false);
function callback(nodes) {
if (self._stop === true) {
return;
}
self.nodes = $.fn.zTree.init(tree.element, setting, nodes);
}
const op = extend({}, o.paras, {
times: 1,
});
o.itemsCreator(op, res => {
if (self._stop === true) {
return;
}
const hasNext = !!res.hasNext,
nodes = res.items || [];
if (nodes.length > 0) {
callback(self._dealWidthNodes(nodes));
}
self.setTipVisible(nodes.length <= 0);
self.loaded();
if (!hasNext) {
self.tip.invisible();
} else {
self.tip.setLoaded();
}
op.times === 1 && self.fireEvent(Events.AFTERINIT);
});
}
// 构造树结构,
initTree(nodes, setting) {
const defaultSetting = {
async: {
enable: false,
},
check: {
enable: false,
},
data: {
key: {
title: "title",
name: "text",
},
simpleData: {
enable: true,
},
},
view: {
showIcon: false,
expandSpeed: "",
nameIsHTML: true,
},
callback: {},
};
this.nodes = $.fn.zTree.init(this.tree.element, setting || defaultSetting, nodes);
}
start() {
this._stop = false;
}
stop() {
this._stop = true;
}
// 生成树方法
stroke(config) {
delete this.options.keyword;
extend(this.options.paras, config);
const setting = this._configSetting();
this._createTree();
this.start();
this._initTree(setting);
}
populate() {
this.stroke(...arguments);
}
hasChecked() {
const treeObj = this.nodes;
return treeObj.getCheckedNodes(true).length > 0;
}
checkAll(checked) {
function setNode(children) {
each(children, (i, child) => {
child.halfCheck = false;
setNode(child.children);
});
}
if (!this.nodes) {
return;
}
each(this.nodes.getNodes(), (i, node) => {
node.halfCheck = false;
setNode(node.children);
});
this.nodes.checkAllNodes(checked);
}
expandAll(flag) {
this.nodes && this.nodes.expandAll(flag);
}
// 设置树节点的状态
setValue(value, param) {
this.checkAll(false);
this.updateValue(value, param);
this.refresh();
}
setSelectedValue(value) {
this.options.paras.selectedValues = deepClone(value || {});
}
updateValue(values, param) {
if (!this.nodes) {
return;
}
param || (param = "value");
const treeObj = this.nodes;
each(values, (v, op) => {
const nodes = treeObj.getNodesByParam(param, v, null);
each(nodes, (j, node) => {
extend(node, { checked: true }, op);
treeObj.updateNode(node);
});
});
}
refresh() {
this.nodes && this.nodes.refresh();
}
getValue() {
if (!this.nodes) {
return null;
}
return this._getSelectedValues();
}
destroyed() {
this.stop();
this.nodes && this.nodes.destroy();
}
}