案例设定:
创建2个树控件,左右排列。
使用拖动的方式,将树节点从左侧树控件拖拽的右侧树控件。
拖拽过程中右侧树控件要进行验证,确认是否可以方式拖拽中的节点。
放置的处理,识别要放置的节点,获取其信息并动态创建新的节点(基于基础类型进行实例化的过程)。
右侧树控件内(实例化之后的节点),同类型节点间支持拖动排序。
Dojo版本 1.10.3
图例1:创建2个树控件,左右排列。
图例2、3:
使用拖动的方式,将树节点从左侧树控件拖拽的右侧树控件。
拖拽过程中右侧树控件要进行验证,确认是否可以方式拖拽中的节点。
图例4
放置的处理,识别要放置的节点,获取其信息并动态创建新的节点(基于基础类型进行实例化的过程)。
图例5:拖拽之后的效果
图例6、7
右侧树控件内(实例化之后的节点),同类型节点间支持拖动排序。
案例代码:
<!DOCTYPE html>
<html > <head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<link rel="stylesheet" type="text/css" href="../dojo_1.10.3/dijit/themes/claro/claro.css"/>
<script type="text/javascript" src="../dojo_1.10.3/dojo/dojo.js" ></script>
<script>
function makeTree(treedata, rootid, treenodeid, dndSkip
, registry, ItemFileWriteStore, TreeStoreModel, dndSource, Tree) {
var store2 = new ItemFileWriteStore({data: treedata});
var treeModel = new TreeStoreModel({
store: store2
, query: {"id": rootid}
, childrenAttrs: ["children"]
});
var tree = new Tree({
model: treeModel, openOnClick: true, showRoot: true, autoExpand: true,
persist: false, style: "width:400px;height:500px;"
}, treenodeid);
tree.startup();
var foo = new dndSource(tree, {betweenThreshold: 5, singular: true});
foo.checkItemAcceptance = function (target, source, position) {
if (dndSkip)
return false;
var flag = false;//判断是否可以拖拽
var tar_type = registry.byNode(target.parentNode).item.type[0];
var src_type = source.tree.selectedItem.type[0];
flag = (tar_type === src_type + '_sp' && position === "over") || (tar_type === src_type && position !== "over");
return flag;
};
// dijit/tree/dndSource.js 源码中明确提及要求开发者覆盖此方法。
foo.itemCreator = function (nodes) {
var rst = new Array();
for (var i = 0; i < nodes.length; i++) {
var node = nodes[i];
var id = '' + Math.floor(Math.random() * 1000);
var item = registry.byNode(node).item;
var rst2 = {id: id, name: item.name + "(" + id + ")", type : item.type};
rst.push(rst2);
}
return rst;
};
}
require(["dijit/registry", "dojo/data/ItemFileWriteStore", "dijit/tree/TreeStoreModel", "dijit/tree/dndSource", "dijit/Tree", "dojo/domReady!"],
function (registry, ItemFileWriteStore, TreeStoreModel, dndSource, Tree) {
if (true) {
var rootid = 'root2';
var treedata = {"identifier": "id", label: "name", "items": [
{id: rootid, name: '零件库', type: 'static', children: [
{id: 'fdj_comp', name: '发动机', type: 'static', children: [
{id: 'fdj1', name: 'A01-发动机', parent: 'fdj_comp', type: 'fdj_comp'}
, {id: 'fdj2', name: 'A02-发动机', parent: 'fdj_comp', type: 'fdj_comp'}
, {id: 'fdj3', name: 'A03-发动机', parent: 'fdj_comp', type: 'fdj_comp'}
]},
{id: 'jss_comp', name: '驾驶室', type: 'static', children: [
{id: 'cm_comp', name: '车门', type: 'static', children: [
{id: 'cm1', name: '左侧车门', parent: 'cm_comp', type: 'cm_comp'},
{id: 'cm2', name: '右侧车门', parent: 'cm_comp', type: 'cm_comp'}
]},
{id: 'ys_com', name: '内饰颜色', type: 'static', children: [
{id: 'heis', name: '黑色', parent: 'ys_com', type: 'ys_com'},
{id: 'mis', name: '米色', parent: 'ys_com', type: 'ys_com'},
{id: 'hongs', name: '红色', parent: 'ys_com', type: 'ys_com'}
]}
]}
]}
]};
makeTree(treedata, rootid, 'funtree1', true, registry, ItemFileWriteStore, TreeStoreModel, dndSource, Tree);
}
if (true) {
var rootid = 'root3';
var treedata = {"identifier": "id", label: "name", "items": [
{id: rootid, name: '配置选择', type: 'static', children: [
{id: 'zfdj', name: '发动机', type: 'fdj_comp_sp', children: []},
{id: 'zcm', name: '车门', type: 'cm_comp_sp', children: []},
{id: 'zys', name: '内饰颜色', type: 'ys_com_sp', children: []}
]}
]};
makeTree(treedata, rootid, 'funtree2', false, registry, ItemFileWriteStore, TreeStoreModel, dndSource, Tree);
}
});
</script>
</head>
<body class="claro">
<table border="1">
<tr>
<td><div id="funtree1"/></td>
<td><div id="funtree2"/></td>
</tr>
</table>
</body>
</html>
重点分析:
1. 使用的dojo组件:
"dijit/registry" //用于获取组件
, "dojo/_base/array" //用于整理数据
, "dojo/data/ItemFileWriteStore" //用于包装数据
, "dijit/tree/TreeStoreModel" //用于转换数据(Tre要求)
, "dijit/tree/dndSource" //用于处理拖拽
, "dijit/Tree" //树控件
2. 构建符合树控件模型接受的数据结构:
var rootid = 'root3';
var treedata = {"identifier": "id", label: "name", "items": [
{id: rootid, name: '配置选择', type: 'static', children: [
{id: 'zfdj', name: '发动机', type: 'fdj_comp_sp', children: []},
{id: 'zcm', name: '车门', type: 'cm_comp_sp', children: []},
{id: 'zys', name: '内饰颜色', type: 'ys_com_sp', children: []}
]}
]};
3. 拖动对象的处理:
3.1 注册:
var foo = new dndSource(
tree, //指向目标树控件
{betweenThreshold: 5, //支持拖动至目标节点的“前位置”和“后位置”
singular: true}); //制定拖动的是1个目标节点(不含其子节点)
3.2 判定拖动的有效性:
foo.checkItemAcceptance = function (target, source, position) { //覆盖函数checkItemAcceptance ,返回true允许放置,返回false不允许放置
if (dndSkip)
return false; //案例使用,左侧树始终不允许放置
var flag = false;//判断是否可以拖拽
var tar_type = registry.byNode(target.parentNode).item.type[0]; //案例使用,右侧树允许同类型节点放置
var src_type = source.tree.selectedItem.type[0];
flag = (tar_type === src_type + '_sp' && position === "over") || (tar_type === src_type && position !== "over");
return flag;
};
3.3 创建目标树节点:
// dijit/tree/dndSource.js 源码中明确提及要求开发者覆盖此方法。
foo.itemCreator = function (nodes) {
var rst = new Array();
for (var i = 0; i < nodes.length; i++) {
var node = nodes[i];
var id = '' + Math.floor(Math.random() * 1000);
var item = registry.byNode(node).item;
var rst2 = {id: id, name: item.name + "(" + id + ")", type : item.type};
rst.push(rst2);
}
return rst;
};
这部分很重要却很难了解到,在dijit/tree/dndSource.js的源码中明确提及需要开发者进行覆盖。而官方文档的介绍内容中并没有提及。
备注:
关于Dojo官方给出的树控件的介绍案例(https://dojotoolkit.org/reference-guide/1.10/dijit/Tree.html#dijit-tree),其使用的是"dojo/store/Memory",其数据结构
var myStore = new Memory({
data: [
{ id: 'world', name:'The earth', type:'planet', population: '6 billion'},
{ id: 'AF', name:'Africa', type:'continent', population:'900 million', area: '30,221,532 sq km',
timezone: '-1 UTC to +4 UTC', parent: 'world'},
……
{ id: 'SA', name:'South America', type:'continent', parent: 'world' }
],
getChildren: function(object){
return this.query({parent: object.id});
}
});
换言之其使用的是一维数组的数据构,加之数据对象中的parent属性指向父节点,完成树模型的构建。但是这个方法在功能上有缺陷:
一次性构建树控件,可以。
树控件中节点的拖动,可以。
树控件中节点的排序,不可以,因为其基础模型是一维数组,无法实现或者难以实现针对某一树节点的子节点序列进行排序的处理。