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怎样改造成为双树的更多相关文章

  1. [luogu3359]改造异或树

    [luogu3359]改造异或树 luogu 和之前某道题类似只有删边的话考虑倒着加边 但是怎么统计答案呢? 我们考虑以任意点为根dfs一遍求出每个点到根的路径异或和s[i] 这样任意两点x,y的路径 ...

  2. bootstrap的导航改造

    在使用bootstrap制作后台时用到了响应式导航条,其中dropdown组件更是用的比较多,用的多需要点击的就多,dropdown默认鼠标左键单击才展开,如果使用鼠标放上去(hover)就展开则会省 ...

  3. bootstrap日期控件(双日期、清空等问题解决)

    bootstrap以它优美的外观和丰富的组件,使它成为目前最流行的前端框架.在项目开发中,我们使用它的日期控件确实遇到了一些问题: 1.日期控件后面两个图标点击触发失效 2.双日期关联问题 3.双日期 ...

  4. luoguP3359 改造异或树 线段树合并

    删边转化为加边 然后每次用线段树合并就行..... 确确实实很简单 然而为什么线段树合并跑不过$splay$的启发式合并,常数稍大了点... 复杂度$O(n \log n)$ #include < ...

  5. 洛谷 P3359 改造异或树

    题目描述 给定一棵n 个点的树,每条边上都有一个权值.现在按顺序删掉所有的n-1条边,每删掉一条边询问当前有多少条路径满足路径上所有边权值异或和为0. 输入输出格式 输入格式: 第一行一个整数n. 接 ...

  6. luoguP3359 改造异或树

    https://www.luogu.org/problemnew/show/P3359 因为 a ^ b ^ b = a,所以我们预处理 1 到所有点的距离,将删边的操作反过来变成加边,对于每一个联通 ...

  7. bootstrap treeview实现菜单树

    本博客,介绍通过Bootstrap的treeview插件实现菜单树的功能. treeview链接:http://www.htmleaf.com/Demo/201502141380.html ORM框架 ...

  8. 基于bootstrap的jQuery多级列表树插件 treeview

    http://www.cnblogs.com/mfc-itblog/p/5233453.html http://www.htmleaf.com/jQuery/Menu-Navigation/20150 ...

  9. 基于bootstrap的jQuery多级列表树插件

    简要教程 bootstrap-treeview是一款效果非常酷的基于bootstrap的jQuery多级列表树插件.该jQuery插件基于Twitter Bootstrap,以简单和优雅的方式来显示一 ...

随机推荐

  1. POJ-1696 Space Ant 凸包版花式水过!

                                                         Space Ant 明天早上最后一科毛概了,竟然毫无复习之意,沉迷刷题无法自拔~~ 题意:说实 ...

  2. Terracotta服务器的不同配置方式

    Terracotta服务器的不同配置方式 博客分类: 企业应用面临的问题 Java&Socket 开源组件的应用 Terracotta双机多机镜像服务器阵列分片模式企业应用 Terracott ...

  3. Spoj-DWARFLOG Manipulate Dwarfs

    Manipulate Dwarfs In a small village beyond seven hills and seven seas, Snow White lives together wi ...

  4. Spring-IOC源码解读2.3-BeanDefinition的注册

    在DefaultListAbleBeanFactory中通过一个HashMap持有载入的BeanDefinition信息 ,这个HashMap的定义在DefaultListAbleBeanFactor ...

  5. R语言入门-画图(二)--heatmap

    一.函数参数: pheatmap参数: treeheight_row #横有多长 treeheight_col #竖有多长 cluster_cols=FLASE #单一方向聚类,也就是只有一边有树状结 ...

  6. Java常用几种加密算法

    对称加密算法是应用较早的加密算法,技术成熟.在对称加密算法中,数据发信方将明文(原始数据)和加密密钥(mi yue)一起经过特殊加密算法处理后,使其变成复杂的加密密文发送出去.收信方收到密文后,若想解 ...

  7. DTD概述

    1. 什么是XML文件 可扩展标记语言,标准通用标记语言的子集,是用于标记电子文件使其具有结构性的标记语言. 2. 什么是dtd文件 DTD(文档类型定义)的作用是定义XML文档的合法构建模块.它使用 ...

  8. Maven的相关问题(一)——settings.xml配置详解

    工作中第一次正式接触maven,虽然之前在学习时有遇到过,但是对于maven的认识和理解确实太浅薄,仅仅局限于机械式的操,纸上得来终觉浅,绝知此事要躬行···古人诚不欺我也~ 下面先贴一个找到的一个非 ...

  9. mysql explain 的type解释

    原文:http://blog.csdn.net/github_26672553/article/details/52058782 Explain命令 用于分析sql语句的执行情况和成本预估 今天我们重 ...

  10. Xcode not building app with changes incorporated

    Did you clean the build folder by pressing command while the cursor is on the clean option? Are you ...