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. Luogu【P1901】发射站(单调栈)

    题目链接 题目说明比自己矮的塔收不到自己的能量,摆明了就是单调栈呗. 把比自己矮的全都从栈里弹出去,于是碰到第一个比自己高的.让他接受自己发射的能量. 当然由于发射站发射的能量有两个方向,所以正反两遍 ...

  2. HDU 5073 Galaxy ——乱搞

    [题目分析] 练习赛的T1. 只要看懂样例就可以猜结论了. 然后大胆猜测剩下的星星是一段,其余的都移到重心上去. 所以只要把计算的式子变形一下就很好维护了. 居然没有1A [代码] #include ...

  3. [luoguP2862] [USACO06JAN]把牛Corral the Cows(二分 + 乱搞)

    传送门 可以二分边长 然后另开两个数组,把x从小到大排序,把y从小到大排序 枚举x,可以得到正方形的长 枚举y,看看从这个y开始,往上能够到达多少个点,可以用类似队列来搞 其实发现算法的本质之后,x可 ...

  4. GFS, HDFS, Blob File System架构对比

    分布式文件系统很多,包括GFS,HDFS,淘宝开源的TFS,Tencent用于相册存储的TFS (Tencent FS,为了便于区别,后续称为QFS),以及Facebook Haystack.其中,T ...

  5. spring security 登录、权限管理配置

    登录流程 1)容器启动(MySecurityMetadataSource:loadResourceDefine加载系统资源与权限列表)  2)用户发出请求  3)过滤器拦截(MySecurityFil ...

  6. uva 11178二维几何(点与直线、点积叉积)

    Problem D Morley’s Theorem Input: Standard Input Output: Standard Output Morley’s theorem states tha ...

  7. LoadBitmap(IDB_BITMAP1) -- 未定义标识符 IDB_BITMAP1

    错误原因:1:头文件没有加入 #include"resource.h" 2:没有导入该资源或者资源ID错误

  8. CatchTheCaw ----广搜入门

    抓住那头牛(POJ3278)农夫知道一头牛的位置,想要抓住它.农夫和牛都位于数轴上,农夫起始位于点N(0<=N<=100000),牛位于点K(0<=K<=100000).农夫有 ...

  9. XPath中的轴

    这个博客中有一系列的例子,不仅有child的例子:http://www.cnblogs.com/zhaozhan/archive/2009/09/10/1563723.html XPath 是一门在 ...

  10. Andrew Stankevich's Contest (21) J dp+组合数

    坑爹的,,组合数模板,,, 6132 njczy2010 1412 Accepted 5572 MS 50620 KB C++ 1844 B 2014-10-02 21:41:15 J - 2-3 T ...