441 lines
11 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import Control from './Control';
import UI from './Manager';
/**
* 树状控件
* @author tengge / https://github.com/tengge1
* @param {*} options
*/
function Tree(options = {}) {
Control.call(this, options);
this.data = options.data || []; // [{ value: '值', text: '文本', expand: 'true/false, 默认关闭', draggable: 'true/false, 默认不可拖动', 其他属性 }, ...]
this.cls = options.cls || 'Tree';
this.onClick = options.onClick || null;
this.onDblClick = options.onDblClick || null;
this.onDrag = options.onDrag || null;
this._selected = null;
this._nodes = {}; // value: li
this._expands = {}; // value: true/false, 记录每个节点展开关闭状态
};
Tree.prototype = Object.create(Control.prototype);
Tree.prototype.constructor = Tree;
Tree.prototype.render = function () {
if (this.dom === undefined) {
this.dom = document.createElement('ul');
this.parent.appendChild(this.dom);
Object.assign(this.dom, {
className: this.cls
});
}
this._clearNode(this.dom);
this.data.forEach(n => {
this._createNode(n, this.dom);
});
};
Tree.prototype._createNode = function (data, dom) {
var li = document.createElement('li');
dom.appendChild(li);
if (data.value === undefined) {
console.warn(`Tree: data.value is not defined. Something unwanted may happen.`);
}
var value = data.value || '';
var text = data.text || '';
var leaf = !Array.isArray(data.children) || data.children.length === 0;
var expand = data.expand || this._expands[value] === true;
var draggable = data.draggable || false;
var cls = data.cls || '';
data.leaf = leaf;
data.expand = expand;
Object.assign(li, {
className: 'Node ' + cls,
data: data
});
li.setAttribute('title', text);
if (draggable) {
li.setAttribute('draggable', draggable);
}
// 刷新前已经选中的节点仍然选中
if (this._selected && value === this._selected.value) {
li.classList.add('selected');
}
li.addEventListener('click', this._onClick.bind(this));
li.addEventListener('dblclick', this._onDblClick.bind(this));
li.addEventListener('drag', this._onDrag.bind(this), false);
li.addEventListener('dragstart', this._onDragStart.bind(this), false);
li.addEventListener('dragover', this._onDragOver.bind(this), false);
li.addEventListener('dragleave', this._onDragLeave.bind(this), false);
li.addEventListener('drop', this._onDrop.bind(this), false);
this._nodes[value] = li;
var icon = document.createElement('i');
if (!leaf && expand) { // 非叶子节点展开
icon.className = 'iconfont icon-down-triangle';
} else if (!leaf && !expand) { // 非叶子节点关闭
icon.className = 'iconfont icon-right-triangle';
} else { // 叶子节点
icon.className = 'iconfont icon-rect';
icon.style.visibility = 'hidden';
}
icon.addEventListener('click', this._toggleNode.bind(this));
li.appendChild(icon);
// 链接
var a = document.createElement('a');
a.setAttribute('href', 'javascript:;');
a.innerHTML = text;
li.appendChild(a);
if (!leaf) {
var ul = document.createElement('ul');
ul.className = 'SubTree';
ul.style.display = expand ? '' : 'none';
li.appendChild(ul);
data.children.forEach(n => {
this._createNode(n, ul);
});
}
};
Tree.prototype._clearNode = function (dom) {
if (dom.classList.contains('Tree')) { // 树
while (dom.children.length) {
this._clearNode(dom.children[0]);
dom.removeChild(dom.children[0]);
}
} else if (dom.classList.contains('SubTree')) { // 子树
while (dom.children.length) {
this._clearNode(dom.children[0]);
dom.removeChild(dom.children[0]);
}
} else if (dom.classList.contains('Node')) { // 节点
delete this._nodes[dom.data.value];
dom.removeEventListener('click', this._onClick);
dom.removeEventListener('dblclick', this._onDblClick);
dom.removeEventListener('drag', this._onDrag);
dom.removeEventListener('dragstart', this._onDragStart);
dom.removeEventListener('dragover', this._onDragOver);
dom.removeEventListener('dragleave', this._onDragLeave);
dom.removeEventListener('drop', this._onDrop);
var icon = dom.children[0];
icon.removeEventListener('click', this._toggleNode);
} else {
console.warn(`Tree: Unknown node type.`);
}
};
Tree.prototype.getValue = function () {
return this.data;
};
Tree.prototype.setValue = function (value) {
this.data = value;
this.render();
};
/**
* 根据value获取节点数据
* @param {*} value
*/
Tree.prototype.getNode = function (value) {
var li = this._nodes[value];
if (!li) {
return null;
}
return li;
};
/**
* 展开节点
* @param {*} value
*/
Tree.prototype.expand = function (value) {
var li = this.getNode(value);
if (!li) {
return;
}
var data = li.data;
if (data.leaf || data.expand) {
return;
}
data.expand = true;
this._expands[data.value] = true;
for (var i = 0; i < li.children.length; i++) {
var node = li.children[i];
if (node.classList.contains('iconfont')) {
node.classList.remove('icon-right-triangle');
node.classList.add('icon-down-triangle');
}
if (node.classList.contains('SubTree')) {
node.style.display = '';
}
}
};
/**
* 折叠节点
* @param {*} value
*/
Tree.prototype.collapse = function (value) {
var li = this.getNode(value);
if (!li) {
return;
}
var data = li.data;
if (data.leaf || !data.expand) {
return;
}
data.expand = false;
this._expands[data.value] = false;
for (var i = 0; i < li.children.length; i++) {
var node = li.children[i];
if (node.classList.contains('iconfont')) {
node.classList.remove('icon-down-triangle');
node.classList.add('icon-right-triangle');
}
if (node.classList.contains('SubTree')) {
node.style.display = 'none';
}
}
};
Tree.prototype.getSelected = function () {
return this._selected;
};
/**
* 根据过滤器查找节点
* @param {*} filter
*/
Tree.prototype.find = function (filter) {
return Object.values(this._nodes).map(n => n.data).filter(filter);
};
/**
* 根据value选中节点
* @param {*} value
*/
Tree.prototype.select = function (value) {
var li = this.getNode(value);
if (!li) {
return;
}
// 移除选中
if (this._selected) {
this.unselect(this._selected.value);
}
this._selected = li.data;
li.classList.add('selected');
this._expandSelected(li);
this._scrollSelected(li);
};
/**
* 展开选中的节点的所有父节点
* @param {*} dom
* @param {*} isParent
*/
Tree.prototype._expandSelected = function (dom, isParent = false) {
if (dom.classList.contains('Tree')) { // 根节点,默认展开
return;
} else if (dom.classList.contains('SubTree')) { // 子树,展开父节点
this._expandSelected(dom.parentNode, true);
} else if (dom.classList.contains('Node')) { // 节点,展开
if (isParent) {
this.expand(dom.data.value);
}
this._expandSelected(dom.parentNode, true);
} else {
console.warn(`Tree: Unknown node.`);
}
};
/**
* 滚动到选中的节点
* @param {*} dom
*/
Tree.prototype._scrollSelected = function (dom) {
var container = this.dom.parentNode;
var y = dom.offsetTop - container.offsetTop;
var bottomY = y + 24; // dom.offsetHeight
var minScroll = bottomY - container.offsetHeight;
if (container.scrollTop > y) { // 选中节点在当前位置上面
container.scrollTop = y - 8;
} else if (container.scrollTop < minScroll) { // 选中节点在当前位置下面
container.scrollTop = minScroll + 8;
}
};
/**
* 取消选中节点
* @param {*} value
*/
Tree.prototype.unselect = function (value) {
var li = this.getNode(value);
if (!li) {
return;
}
this._selected = null;
li.classList.remove('selected');
};
Tree.prototype._onClick = function (event) {
var data = event.currentTarget.data;
event.stopPropagation();
this.select(data.value);
if (typeof (this.onClick) === 'function') {
this.onClick(data, event);
}
};
Tree.prototype._onDblClick = function (event) {
var data = event.currentTarget.data;
event.stopPropagation();
if (typeof (this.onDblClick) === 'function') {
this.onDblClick(data, event);
}
};
Tree.prototype._toggleNode = function (event) {
var li = event.currentTarget.parentNode;
var data = li.data;
event.stopPropagation();
if (data.leaf) {
return;
} else if (data.expand) {
this.collapse(data.value);
} else {
this.expand(data.value);
}
};
// --------------------- 拖拽事件 ---------------------------
Tree.prototype._onDrag = function (event) {
event.stopPropagation();
this.currentDrag = event.currentTarget;
};
Tree.prototype._onDragStart = function (event) {
event.stopPropagation();
event.dataTransfer.setData('text', 'foo');
};
Tree.prototype._onDragOver = function (event) {
event.preventDefault();
event.stopPropagation();
var target = event.currentTarget;
if (target === this.currentDrag) {
return;
}
var area = event.offsetY / target.clientHeight;
if (area < 0.25) {
target.classList.add('dragTop');
} else if (area > 0.75) {
target.classList.add('dragBottom');
} else {
target.classList.add('drag');
}
};
Tree.prototype._onDragLeave = function (event) {
event.preventDefault();
event.stopPropagation();
var target = event.currentTarget;
if (target === this.currentDrag) {
return;
}
target.classList.remove('dragTop');
target.classList.remove('dragBottom');
target.classList.remove('drag');
};
Tree.prototype._onDrop = function (event) {
event.preventDefault();
event.stopPropagation();
var target = event.currentTarget;
if (target === this.currentDrag) {
return;
}
target.classList.remove('dragTop');
target.classList.remove('dragBottom');
target.classList.remove('drag');
if (typeof (this.onDrag) === 'function') {
var area = event.offsetY / target.clientHeight;
if (area < 0.25) { // 放在当前元素前面
this.onDrag(
this.currentDrag.data, // 拖动要素
target.parentNode.parentNode.data, // 新位置父级
target.data, // 新位置索引
); // 拖动, 父级, 索引
} else if (area > 0.75) { // 放在当前元素后面
this.onDrag(
this.currentDrag.data,
target.parentNode.parentNode.data,
target.nextSibling == null ? null : target.nextSibling.data, // target.nextSibling为null说明是最后一个位置
);
} else { // 成为该元素子级
this.onDrag(
this.currentDrag.data,
target.data,
null,
);
}
}
};
UI.addXType('tree', Tree);
export default Tree;