BootStrap-DualListBox怎样改造成为双树

2023-06-02,,

BootStrap-DualListBox能够实现将所选择的列表项显示到右边,未选的列表项显示到左边。

但是左右两边的下拉框中都是单级列表。如果要实现将两边都是树(缩进树),选择某个节点时,其子节点也进到右边,不选某个节点时,其子节点也都回到左边呢?

实现思路是:

1、在DualListBox每次选择时,都会触发change事件,我们在change中,去处理子节点的选择和未选择。所有处理都通过change事件触发。

2、在处理完后,调用DualListBox的refresh方法。

在具体处理中,需要遍历树的节点数据,来获取树节点,子节点,父节点,并进行递归处理。

为了方便调用,将改进后扩展的代码放到单独的文件中,并扩展了jquery方法,增加BootDualTree方法,实现双树的初始化,加载数据,获取选中值,反向绑定等方法。

调用代码示例如下:查看在线演示

   <head>
<title>Bootstrap Dual Listbox</title>
<link href="bootstrap.min.css" rel="stylesheet">
<!--<link href="http://libs.baidu.com/bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">-->
<link rel="stylesheet" type="text/css" href="../src/prettify.css">
<link rel="stylesheet" type="text/css" href="../src/bootstrap-duallistbox.css">
<script src="jquery.min.js"></script>
<!--<script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>-->
<script src="bootstrap.min.js"></script> <!--<script src="http://libs.baidu.com/bootstrap/3.0.3/js/bootstrap.min.js"></script>-->
<script src="../src/jquery.bootstrap-duallistbox.js"></script>
<script src="bootstrap-dualtree.js"></script>
</head>
<body class="container"> <h2>lh Test</h2>
<p>
Make the dual listbox be dual tree.
</p>
<div>
<form id="lhdemoform" action="#" method="post">
<select multiple="multiple" size="10" name="duallistbox_lhdemo">
<!--<option value="option1">Option 1</option>-->
</select>
<br>
<input type="button" value="初始数据" id="btnAddNew" />
<input type="button" value="获取选中数据" id="btnAddNew1" onclick="alert($('select[name=duallistbox_lhdemo]').bootstrapDualTree('getSelValues',false).join(' '))"/>
<input type="button" value="获取选中叶子节点数据" id="btnAddNew2" onclick="alert($('select[name=duallistbox_lhdemo]').bootstrapDualTree('getSelValues').join(' '))" />
<select multiple="multiple" size="10" name="duallistbox_lhdemo2">
<!--<option value="option1">Option 1</option>-->
</select>
<input type="button" value="获取选中数据" id="btnAddNew3" onclick="alert($('select[name=duallistbox_lhdemo2]').bootstrapDualTree('getSelValues',false).join(' '))" />
<input type="button" value="获取选中叶子节点数据" id="btnAddNew4" onclick="alert($('select[name=duallistbox_lhdemo2]').bootstrapDualTree('getSelValues').join(' '))" />
</form>
<script> //调用示例
var data = {
text: "t1",
value: "v1",
pid: "0",
children: [
{
text: "t11",
value: "v11",
pid: "v1",
children: [
{
text: "t111",
value: "v111",
pid: "v11",
},
{
text: "t112",
value: "v112",
pid: "v11",
children: [
{
text: "t1121",
value: "v1121",
pid: "v112",
},
{
text: "t1122",
value: "v1122",
pid: "v112",
},
],
},
]
},
{
text: "t12",
value: "v12",
pid: "v1",
children: [
{
text: "t121",
value: "v121",
pid: "v12",
},
{
text: "t122",
value: "v122",
pid: "v12",
},
]
},
],
}; var lhdemo = $('select[name="duallistbox_lhdemo"]').bootstrapDualTree({
nonSelectedListLabel: '未选',
selectedListLabel: '已选',
preserveSelectionOnMove: 'moved',
moveOnSelect: true,
//dualTree在dualListbox基础上新增的属性
data: data,//树形节点数据
selValues: ["v1121"], //默认选中节点值,为数组.如果不传,则默认不选中
indentSymbol: "-" //缩进符号,默认为-
});
var lhdemo2 = $('select[name="duallistbox_lhdemo2"]').bootstrapDualTree({
nonSelectedListLabel: '未选',
selectedListLabel: '已选',
preserveSelectionOnMove: 'moved',
moveOnSelect: true,
//dualTree在dualListbox基础上新增的属性
data: data,//树形节点数据
selValues: ["v1121", "v1122"], //默认选中节点值,为数组.如果不传,则默认不选中
indentSymbol: "-" //缩进符号,默认为-
});
$("#btnAddNew").click(function () {
//lhdemo.bootstrapDualTree("loadData", data, ["v1121", "v1122"]);//加载数据方法,可同时传递当前选中值
lhdemo.bootstrapDualTree("setValues", ["v1121", "v1122"]);//设置当前选中值
}); </script>
</div>
</body>

效果如下:

打包的bootstrap-dualtree.js文件代码如下:

 /**
* bootstrapDualTree extended from bootstrapDualListbox
* author: lh 2015-12-10
*/
(function ($, window, document, undefined) {
var pluginName = "bootstrapDualTree";//插件名称
//扩展jquery方法
$.fn[pluginName] = function (options) {
var returns;
var args = arguments;
if (options === undefined || typeof options === 'object') {
return this.each(function () {
if (!$.data(this, "plugin_" + pluginName)) {
$.data(this, "plugin_" + pluginName, new BootstrapDualTree(this, options));
}
});
} else if (typeof options === 'string' && options[0] !== '_' && options !== 'init') {
this.each(function () {
var instance = $.data(this, 'plugin_' + pluginName);
// Tests that there's already a plugin-instance and checks that the requested public method exists
if (instance instanceof BootstrapDualTree && typeof instance[options] === 'function') {
// Call the method of our plugin instance, and pass it the supplied arguments.
returns = instance[options].apply(instance, Array.prototype.slice.call(args, 1));
}
});
};
return returns !== undefined ? returns : this;
}
//定义DualTree对象
function BootstrapDualTree(element, options) { var $e = $(element).bootstrapDualListbox(options);
this.tElement = $e; if (options.data) {
this.data = options.data;
}
if (options.indentSymbol!==undefined) {
this.setting.indentSymbol = options.indentSymbol;
}
if (options.selValues) {
this.selValues = options.selValues;
}
this.init();
var dualTree = this;
//bootstrap dual-listbox 在其发生变化的时候,触发change事件,实现双树都在这个事件中处理
$e.change(function () {
dualTree.refresh();
});
}
//定义可对外提供的方法
BootstrapDualTree.prototype = {
tElement:{},//select元素
data :{},//数据
selValues:[],//选择的节点值
setting:{
indentSymbol: "-",
},
lastOptions :[],//用于记录上一次的下列列表状态,以便通过比较识别移动操作的目标节点有哪些
loadData: function (dataL, selValuesL) {
data = dataL;
selValues = selValuesL || [];
this.init();
},
setValues: function (selValuesL) {
selValues = selValuesL || [];
this.init();
},
getSelValues: function (onlyLeaf) {
if (typeof(onlyLeaf)== "undefined") onlyLeaf = true;
var selValues1 = getSelValues(this.tElement,this.data, onlyLeaf);
return selValues1;
},
init: function () {
//alert(tElement)
this.tElement.find("option").remove();
showData(this.tElement, this.data, this.indentSymbol, 0, this.selValues);
recLastOptions(this.tElement, this);
this.tElement.bootstrapDualListbox("refresh");
if (this.selValues.length > 0) {
updateTreeSelectedStatus(this.tElement,this,this.data, this.selValues);
}
},
refresh: function () {
updateTreeSelectedStatus(this.tElement,this, this.data);
} }; //获取变化事件的方向:向右选择,向左选择
function getChangedDir() {
var dir = "all";
var srcHtml = event.srcElement.outerHTML;
//arrow-right关键字针对点击箭头移动的情形,nonselected-list针对选中时直接移动的情形
if (/arrow-right/.test(srcHtml) || /nonselected-list/.test(srcHtml)) {
dir = "right";
}
else if (/arrow-left/.test(srcHtml) || /selected-list/.test(srcHtml)) {
dir = "left";
}
return dir;
}
//记录上一个所有选项状态
function recLastOptions(tElement,tTree) {
tTree.lastOptions = [];
tElement.find("option").each(function () {
var curNode = $(this);
tTree.lastOptions.push({ value: curNode.attr("value"), selected: curNode.prop("selected") });
});
}
//获取发生变化的节点ID列表
function getChangedIds(tElement, lastOptions, dir) {
var changedIds = [];
if (dir == "right") {//向右,则取新选择的节点
newOptions = tElement.find("option");
for (var i = 0; i < newOptions.length; i++) {
if (newOptions[i].selected && !lastOptions[i].selected)
changedIds.push(lastOptions[i].value)
}
}
else if (dir == "left")//向左,则取新取消的节点
{
newOptions = tElement.find("option");
for (var i = 0; i < newOptions.length; i++) {
if (!newOptions[i].selected && lastOptions[i].selected)
changedIds.push(lastOptions[i].value)
}
}
return changedIds;
} //更新节点选中状态,将选中节点的父节点也都选中;
function updateTreeSelectedStatus(tElement, tTree, data, selValues) {
var dir = selValues && selValues.length > 0 ? "right" : getChangedDir();
var cIds = selValues || getChangedIds(tElement, tTree.lastOptions, dir);
console.log("changed:" + cIds)
if (dir == "right") {
//将所选节点的子节点及其路径上的节点也选中
for (var i = 0; i < cIds.length; i++) {
var node = findNodeById(data, cIds[i]);
console.log("handling-right:")
console.log(node)
selAllChildNodes(tElement, node);
selAcesterNodesInPath(tElement,data, node);
}
}
else if (dir == "left") {
//将所选节点的子节点也都取消选中
for (var i = 0; i < cIds.length; i++) {
var node = findNodeById(data, cIds[i]);
console.log("handling-left:")
console.log(node)
unSelAllChildNodes(tElement, node);
unSelAcesterNodesInPath(tElement,data, node);
}
} //重新添加未选节点及其父节点
//1、记录未选节点及其父节点
var nonSelNodes = [];
tElement.find("option").not(":selected").each(function () {
var curNode = $(this);
nonSelNodes.push(curNode.attr("value"));
while (curNode.length > 0) {
var pOption = tElement.find("option[value='" + curNode.attr("rel") + "']");
if (pOption.length > 0 && nonSelNodes.indexOf(pOption.attr("value")) < 0) nonSelNodes.push(pOption.attr("value"));
curNode = pOption;
}
});
//2、清除未选择的节点
tElement.find("option").not(':selected').remove();
console.log("nonSelNodes:" + nonSelNodes)
//3、重新显示左侧下拉列表
showNonSelData(tElement, data, tTree.setting.indentSymbol, 0, nonSelNodes); //重新显示已选择节点,以保持排序
var selNodes = [];
makeNoDuplicateSelNode(tElement);
var selOptions = tElement.find("option:selected");
for (var n = 0; n < selOptions.length; n++)
selNodes.push(selOptions[n].value);
selOptions.remove();
console.log("selNodes:" + selNodes)
showSelData(tElement, data, tTree.setting.indentSymbol, 0, selNodes); tElement.bootstrapDualListbox("refresh");
//记录新的下拉框状态
recLastOptions(tElement, tTree);
}
//递归显示所有节点
function showData(tElement, node,indentSymbol, depth, selValues) {
var selValues = selValues || [];
var withdraw = "";
for (var i = 0; i < depth; i++)
withdraw += indentSymbol;
tElement.append("<option value='" + node.value + "' rel='" + node.pid + "' " + (selValues.indexOf(node.value) >= 0 ? "selected" : "") + ">" + withdraw + node.text + "</option>");
if (node.children) {
for (var n = 0; n < node.children.length; n++) {
showData(tElement, node.children[n],indentSymbol, depth + 1, selValues);
}
}
}
//递归显示未选择节点
function showNonSelData(tElement, node, indentSymbol, depth, nonSelNodes) {
var withdraw = "";
for (var i = 0; i < depth; i++)
withdraw += indentSymbol;
if (nonSelNodes.indexOf(node.value) >= 0 && tElement.find("option[value='" + node.value + "']").not(":selected").length == 0) {
tElement.append("<option value='" + node.value + "' rel='" + node.pid + "'>" + withdraw + node.text + "</option>");
if (node.children) {
for (var n = 0; n < node.children.length; n++) {
showNonSelData(tElement, node.children[n],indentSymbol, depth + 1, nonSelNodes);
}
}
}
}
//递归显示已选择节点
function showSelData(tElement, node, indentSymbol, depth, selNodes) {
var withdraw = "";
for (var i = 0; i < depth; i++)
withdraw += indentSymbol;
if (selNodes.indexOf(node.value) >= 0 && tElement.find("option[value='" + node.value + "']:selected").length == 0) {
tElement.append("<option value='" + node.value + "' rel='" + node.pid + "' selected>" + withdraw + node.text + "</option>");
if (node.children) {
for (var n = 0; n < node.children.length; n++) {
showSelData(tElement, node.children[n], indentSymbol,depth + 1, selNodes);
}
}
}
}
//去掉已选择的重复节点
function makeNoDuplicateSelNode(tElement) {
tElement.find("option:selected").each(function () {
var curNode = $(this);
var options = tElement.find("option[value='" + curNode.attr("value") + "']:selected");
if (options.length > 1) {
for (var i = options.length; i > 0; i--)
$(options[i]).remove();
}
});
}
//如果一个节点选择了,则选中其子节点
function selAllChildNodes(tElement, node) {
if (node.children) {
for (var n = 0; n < node.children.length; n++) {
tElement.find("option[value='" + node.children[n].value + "']").prop("selected", true);
selAllChildNodes(tElement, node.children[n]);
}
}
}
//如果一个节点取消选择了,则取消选中其子节点
function unSelAllChildNodes(tElement, node) {
if (node.children) {
for (var n = 0; n < node.children.length; n++) {
tElement.find("option[value='" + node.children[n].value + "']").prop("selected", false);
unSelAllChildNodes(tElement, node.children[n]);
}
}
}
//获取选中的值列表
function getSelValues(tElement, node, onlyLeaf) {
var selValuesTmp = [];
tElement.find("option[value='" + node.value + "']").each(function () {
if ($(this).prop("selected")) {
if (!node.children || node.children.length == 0 || !onlyLeaf) {
selValuesTmp.push(node.value);
}
if (node.children) {
for (var n = 0; n < node.children.length; n++) {
selValuesTmp = selValuesTmp.concat(getSelValues(tElement,node.children[n], onlyLeaf));
}
}
}
});
return selValuesTmp;
}
//选中一个节点的路径上的祖先节点
function selAcesterNodesInPath(tElement,root, node) {
var curNode = node;
while (curNode.pid != "0") {
curNode = findNodeById(root, curNode.pid);
var pOption = tElement.find("option[value='" + curNode.value + "']");
if (pOption.length > 0) pOption.prop("selected", true);
}
}
//取消一个节点的路径上的祖先节点,这些节点没有子节点被选中
function unSelAcesterNodesInPath(tElement, root, node) {
var curNode = node;
while (curNode.pid != "0") {
curNode = findNodeById(root, curNode.pid);
if (!hasSelChildrenNodes(tElement, curNode)) {
var pOption = tElement.find("option[value='" + curNode.value + "']");
if (pOption.length > 0) pOption.prop("selected", false);
}
}
}
//从树中寻找某个id的节点
function findNodeById(node, id) {
if (node.value == id) {
return node;
}
else {
if (node.children) {
for (var i = 0; i < node.children.length; i++) {
var rsNode = findNodeById(node.children[i], id);
if (rsNode != null) return rsNode;
}
}
}
return null;
}
//判断某个节点的子节点是否被选中
function hasSelChildrenNodes(tElement, node) {
if (node.children) {
for (var i = 0; i < node.children.length; i++) {
var pOption = tElement.find("option[value='" + node.children[i].value + "']:selected");
if (pOption.length > 0) return true;
}
}
return false;
}
})(jQuery, window, document);

BootStrap-DualListBox怎样改造成为双树的相关教程结束。

《BootStrap-DualListBox怎样改造成为双树.doc》

下载本文的Word格式文档,以方便收藏与打印。