您的位置:首页 > Web前端 > BootStrap

BootStrap-DualListBox怎样改造成为双树

2020-03-01 18:01 579 查看

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

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

实现思路是:

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

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

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

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

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

1   <head>
2     <title>Bootstrap Dual Listbox</title>
3     <link href="bootstrap.min.css" rel="stylesheet">
4       <!--<link href="http://libs.baidu.com/bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">-->
5     <link rel="stylesheet" type="text/css" href="../src/prettify.css">
6     <link rel="stylesheet" type="text/css" href="../src/bootstrap-duallistbox.css">
7     <script src="jquery.min.js"></script>
8       <!--<script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>-->
9     <script src="bootstrap.min.js"></script>
10
11       <!--<script src="http://libs.baidu.com/bootstrap/3.0.3/js/bootstrap.min.js"></script>-->
12     <script src="../src/jquery.bootstrap-duallistbox.js"></script>
13       <script src="bootstrap-dualtree.js"></script>
14   </head>
15   <body class="container">
16
17
18
19       <h2>lh Test</h2>
20       <p>
21           Make the dual listbox be dual tree.
22       </p>
23       <div>
24           <form id="lhdemoform" action="#" method="post">
25               <select multiple="multiple" size="10" name="duallistbox_lhdemo">
26                   <!--<option value="option1">Option 1</option>-->
27               </select>
28               <br>
29               <input type="button" value="初始数据" id="btnAddNew" />
30               <input type="button" value="获取选中数据" id="btnAddNew1"  onclick="alert($('select[name=duallistbox_lhdemo]').bootstrapDualTree('getSelValues',false).join(' '))"/>
31               <input type="button" value="获取选中叶子节点数据" id="btnAddNew2" onclick="alert($('select[name=duallistbox_lhdemo]').bootstrapDualTree('getSelValues').join(' '))" />
32               <select multiple="multiple" size="10" name="duallistbox_lhdemo2">
33                   <!--<option value="option1">Option 1</option>-->
34               </select>
35               <input type="button" value="获取选中数据" id="btnAddNew3" onclick="alert($('select[name=duallistbox_lhdemo2]').bootstrapDualTree('getSelValues',false).join(' '))" />
36               <input type="button" value="获取选中叶子节点数据" id="btnAddNew4" onclick="alert($('select[name=duallistbox_lhdemo2]').bootstrapDualTree('getSelValues').join(' '))" />
37           </form>
38           <script>
39
40               //调用示例
41               var data = {
42                   text: "t1",
43                   value: "v1",
44                   pid: "0",
45                   children: [
46                       {
47                           text: "t11",
48                           value: "v11",
49                           pid: "v1",
50                           children: [
51                             {
52                                 text: "t111",
53                                 value: "v111",
54                                 pid: "v11",
55                             },
56                             {
57                                 text: "t112",
58                                 value: "v112",
59                                 pid: "v11",
60                                 children: [
61                                     {
62                                         text: "t1121",
63                                         value: "v1121",
64                                         pid: "v112",
65                                     },
66                                     {
67                                         text: "t1122",
68                                         value: "v1122",
69                                         pid: "v112",
70                                     },
71                                 ],
72                             },
73                           ]
74                       },
75                       {
76                           text: "t12",
77                           value: "v12",
78                           pid: "v1",
79                           children: [
80                             {
81                                 text: "t121",
82                                 value: "v121",
83                                 pid: "v12",
84                             },
85                             {
86                                 text: "t122",
87                                 value: "v122",
88                                 pid: "v12",
89                             },
90                           ]
91                       },
92                   ],
93               };
94
95               var lhdemo = $('select[name="duallistbox_lhdemo"]').bootstrapDualTree({
96                   nonSelectedListLabel: '未选',
97                   selectedListLabel: '已选',
98                   preserveSelectionOnMove: 'moved',
99                   moveOnSelect: true,
100                   //dualTree在dualListbox基础上新增的属性
101                   data: data,//树形节点数据
102                   selValues: ["v1121"], //默认选中节点值,为数组.如果不传,则默认不选中
103                   indentSymbol: "-" //缩进符号,默认为-
104               });
105               var lhdemo2 = $('select[name="duallistbox_lhdemo2"]').bootstrapDualTree({
106                   nonSelectedListLabel: '未选',
107                   selectedListLabel: '已选',
108                   preserveSelectionOnMove: 'moved',
109                   moveOnSelect: true,
110                   //dualTree在dualListbox基础上新增的属性
111                   data: data,//树形节点数据
112                   selValues: ["v1121", "v1122"], //默认选中节点值,为数组.如果不传,则默认不选中
113                   indentSymbol: "-" //缩进符号,默认为-
114               });
115               $("#btnAddNew").click(function () {
116                   //lhdemo.bootstrapDualTree("loadData", data, ["v1121", "v1122"]);//加载数据方法,可同时传递当前选中值
117                   lhdemo.bootstrapDualTree("setValues", ["v1121", "v1122"]);//设置当前选中值
118               });
119
120
121           </script>
122       </div>
123 </body>

效果如下:

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

1 /**
2 * bootstrapDualTree extended from bootstrapDualListbox
3 * author: lh 2015-12-10
4 */
5 (function ($, window, document, undefined) {
6     var pluginName = "bootstrapDualTree";//插件名称
7     //扩展jquery方法
8     $.fn[pluginName] = function (options) {
9         var returns;
10         var args = arguments;
11         if (options === undefined || typeof options === 'object') {
12             return this.each(function () {
13                 if (!$.data(this, "plugin_" + pluginName)) {
14                     $.data(this, "plugin_" + pluginName, new BootstrapDualTree(this, options));
15                 }
16             });
17         } else if (typeof options === 'string' && options[0] !== '_' && options !== 'init') {
18             this.each(function () {
19                 var instance = $.data(this, 'plugin_' + pluginName);
20                 // Tests that there's already a plugin-instance and checks that the requested public method exists
21                 if (instance instanceof BootstrapDualTree && typeof instance[options] === 'function') {
22                     // Call the method of our plugin instance, and pass it the supplied arguments.
23                     returns = instance[options].apply(instance, Array.prototype.slice.call(args, 1));
24                 }
25             });
26         };
27         return returns !== undefined ? returns : this;
28     }
29     //定义DualTree对象
30     function BootstrapDualTree(element, options) {
31
32         var $e = $(element).bootstrapDualListbox(options);
33         this.tElement = $e;
34
35         if (options.data) {
36             this.data = options.data;
37         }
38         if (options.indentSymbol!==undefined) {
39             this.setting.indentSymbol = options.indentSymbol;
40         }
41         if (options.selValues) {
42             this.selValues = options.selValues;
43         }
44         this.init();
45         var dualTree = this;
46         //bootstrap dual-listbox 在其发生变化的时候,触发change事件,实现双树都在这个事件中处理
47         $e.change(function () {
48             dualTree.refresh();
49         });
50     }
51     //定义可对外提供的方法
52     BootstrapDualTree.prototype = {
53         tElement:{},//select元素
54         data :{},//数据
55     selValues:[],//选择的节点值
56     setting:{
57         indentSymbol: "-",
58     },
59     lastOptions :[],//用于记录上一次的下列列表状态,以便通过比较识别移动操作的目标节点有哪些
60         loadData: function (dataL, selValuesL) {
61             data = dataL;
62             selValues = selValuesL || [];
63             this.init();
64         },
65         setValues: function (selValuesL) {
66             selValues = selValuesL || [];
67             this.init();
68         },
69         getSelValues: function (onlyLeaf) {
70             if (typeof(onlyLeaf)== "undefined") onlyLeaf = true;
71             var selValues1 = getSelValues(this.tElement,this.data, onlyLeaf);
72             return selValues1;
73         },
74         init: function () {
75             //alert(tElement)
76             this.tElement.find("option").remove();
77             showData(this.tElement, this.data, this.indentSymbol, 0, this.selValues);
78             recLastOptions(this.tElement, this);
79             this.tElement.bootstrapDualListbox("refresh");
80             if (this.selValues.length > 0) {
81                 updateTreeSelectedStatus(this.tElement,this,this.data, this.selValues);
82             }
83         },
84         refresh: function () {
85             updateTreeSelectedStatus(this.tElement,this, this.data);
86         }
87
88     };
89
90     //获取变化事件的方向:向右选择,向左选择
91     function getChangedDir() {
92         var dir = "all";
93         var srcHtml = event.srcElement.outerHTML;
94         //arrow-right关键字针对点击箭头移动的情形,nonselected-list针对选中时直接移动的情形
95         if (/arrow-right/.test(srcHtml) || /nonselected-list/.test(srcHtml)) {
96             dir = "right";
97         }
98         else if (/arrow-left/.test(srcHtml) || /selected-list/.test(srcHtml)) {
99             dir = "left";
100         }
101         return dir;
102     }
103     //记录上一个所有选项状态
104     function recLastOptions(tElement,tTree) {
105         tTree.lastOptions = [];
106         tElement.find("option").each(function () {
107             var curNode = $(this);
108             tTree.lastOptions.push({ value: curNode.attr("value"), selected: curNode.prop("selected") });
109         });
110     }
111     //获取发生变化的节点ID列表
112     function getChangedIds(tElement, lastOptions, dir) {
113         var changedIds = [];
114         if (dir == "right") {//向右,则取新选择的节点
115             newOptions = tElement.find("option");
116             for (var i = 0; i < newOptions.length; i++) {
117                 if (newOptions[i].selected && !lastOptions[i].selected)
118                     changedIds.push(lastOptions[i].value)
119             }
120         }
121         else if (dir == "left")//向左,则取新取消的节点
122         {
123             newOptions = tElement.find("option");
124             for (var i = 0; i < newOptions.length; i++) {
125                 if (!newOptions[i].selected && lastOptions[i].selected)
126                     changedIds.push(lastOptions[i].value)
127             }
128         }
129         return changedIds;
130     }
131
132     //更新节点选中状态,将选中节点的父节点也都选中;
133     function updateTreeSelectedStatus(tElement, tTree, data, selValues) {
134         var dir = selValues && selValues.length > 0 ? "right" : getChangedDir();
135         var cIds = selValues || getChangedIds(tElement, tTree.lastOptions, dir);
136         console.log("changed:" + cIds)
137         if (dir == "right") {
138             //将所选节点的子节点及其路径上的节点也选中
139             for (var i = 0; i < cIds.length; i++) {
140                 var node = findNodeById(data, cIds[i]);
141                 console.log("handling-right:")
142                 console.log(node)
143                 selAllChildNodes(tElement, node);
144                 selAcesterNodesInPath(tElement,data, node);
145             }
146         }
147         else if (dir == "left") {
148             //将所选节点的子节点也都取消选中
149             for (var i = 0; i < cIds.length; i++) {
150                 var node = findNodeById(data, cIds[i]);
151                 console.log("handling-left:")
152                 console.log(node)
153                 unSelAllChildNodes(tElement, node);
154                 unSelAcesterNodesInPath(tElement,data, node);
155             }
156         }
157
158         //重新添加未选节点及其父节点
159         //1、记录未选节点及其父节点
160         var nonSelNodes = [];
161         tElement.find("option").not(":selected").each(function () {
162             var curNode = $(this);
163             nonSelNodes.push(curNode.attr("value"));
164             while (curNode.length > 0) {
165                 var pOption = tElement.find("option[value='" + curNode.attr("rel") + "']");
166                 if (pOption.length > 0 && nonSelNodes.indexOf(pOption.attr("value")) < 0) nonSelNodes.push(pOption.attr("value"));
167                 curNode = pOption;
168             }
169         });
170         //2、清除未选择的节点
171         tElement.find("option").not(':selected').remove();
172         console.log("nonSelNodes:" + nonSelNodes)
173         //3、重新显示左侧下拉列表
174         showNonSelData(tElement, data, tTree.setting.indentSymbol, 0, nonSelNodes);
175
176         //重新显示已选择节点,以保持排序
177         var selNodes = [];
178         makeNoDuplicateSelNode(tElement);
179         var selOptions = tElement.find("option:selected");
180         for (var n = 0; n < selOptions.length; n++)
181             selNodes.push(selOptions
.value);
182         selOptions.remove();
183         console.log("selNodes:" + selNodes)
184         showSelData(tElement, data, tTree.setting.indentSymbol, 0, selNodes);
185
186         tElement.bootstrapDualListbox("refresh");
187         //记录新的下拉框状态
188         recLastOptions(tElement, tTree);
189     }
190     //递归显示所有节点
191     function showData(tElement, node,indentSymbol, depth, selValues) {
192         var selValues = selValues || [];
193         var withdraw = "";
194         for (var i = 0; i < depth; i++)
195             withdraw += indentSymbol;
196         tElement.append("<option value='" + node.value + "' rel='" + node.pid + "' " + (selValues.indexOf(node.value) >= 0 ? "selected" : "") + ">" + withdraw + node.text + "</option>");
197         if (node.children) {
198             for (var n = 0; n < node.children.length; n++) {
199                 showData(tElement, node.children
,indentSymbol, depth + 1, selValues);
200             }
201         }
202     }
203     //递归显示未选择节点
204     function showNonSelData(tElement, node, indentSymbol, depth, nonSelNodes) {
205         var withdraw = "";
206         for (var i = 0; i < depth; i++)
207             withdraw += indentSymbol;
208         if (nonSelNodes.indexOf(node.value) >= 0 && tElement.find("option[value='" + node.value + "']").not(":selected").length == 0) {
209             tElement.append("<option value='" + node.value + "' rel='" + node.pid + "'>" + withdraw + node.text + "</option>");
210             if (node.children) {
211                 for (var n = 0; n < node.children.length; n++) {
212                     showNonSelData(tElement, node.children
,indentSymbol, depth + 1, nonSelNodes);
213                 }
214             }
215         }
216     }
217     //递归显示已选择节点
218     function showSelData(tElement, node, indentSymbol, depth, selNodes) {
219         var withdraw = "";
220         for (var i = 0; i < depth; i++)
221             withdraw += indentSymbol;
222         if (selNodes.indexOf(node.value) >= 0 && tElement.find("option[value='" + node.value + "']:selected").length == 0) {
223             tElement.append("<option value='" + node.value + "' rel='" + node.pid + "' selected>" + withdraw + node.text + "</option>");
224             if (node.children) {
225                 for (var n = 0; n < node.children.length; n++) {
226                     showSelData(tElement, node.children
, indentSymbol,depth + 1, selNodes);
227                 }
228             }
229         }
230     }
231     //去掉已选择的重复节点
232     function makeNoDuplicateSelNode(tElement) {
233         tElement.find("option:selected").each(function () {
234             var curNode = $(this);
235             var options = tElement.find("option[value='" + curNode.attr("value") + "']:selected");
236             if (options.length > 1) {
237                 for (var i = options.length; i > 0; i--)
238                     $(options[i]).remove();
239             }
240         });
241     }
242     //如果一个节点选择了,则选中其子节点
243     function selAllChildNodes(tElement, node) {
244         if (node.children) {
245             for (var n = 0; n < node.children.length; n++) {
246                 tElement.find("option[value='" + node.children
.value + "']").prop("selected", true);
247                 selAllChildNodes(tElement, node.children
);
248             }
249         }
250     }
251     //如果一个节点取消选择了,则取消选中其子节点
252     function unSelAllChildNodes(tElement, node) {
253         if (node.children) {
254             for (var n = 0; n < node.children.length; n++) {
255                 tElement.find("option[value='" + node.children
.value + "']").prop("selected", false);
256                 unSelAllChildNodes(tElement, node.children
);
257             }
258         }
259     }
260     //获取选中的值列表
261     function getSelValues(tElement, node, onlyLeaf) {
262         var selValuesTmp = [];
263         tElement.find("option[value='" + node.value + "']").each(function () {
264             if ($(this).prop("selected")) {
265                 if (!node.children || node.children.length == 0 || !onlyLeaf) {
266                     selValuesTmp.push(node.value);
267                 }
268                 if (node.children) {
269                     for (var n = 0; n < node.children.length; n++) {
270                         selValuesTmp = selValuesTmp.concat(getSelValues(tElement,node.children
, onlyLeaf));
271                     }
272                 }
273             }
274         });
275         return selValuesTmp;
276     }
277     //选中一个节点的路径上的祖先节点
278     function selAcesterNodesInPath(tElement,root, node) {
279         var curNode = node;
280         while (curNode.pid != "0") {
281             curNode = findNodeById(root, curNode.pid);
282             var pOption = tElement.find("option[value='" + curNode.value + "']");
283             if (pOption.length > 0) pOption.prop("selected", true);
284         }
285     }
286     //取消一个节点的路径上的祖先节点,这些节点没有子节点被选中
287     function unSelAcesterNodesInPath(tElement, root, node) {
288         var curNode = node;
289         while (curNode.pid != "0") {
290             curNode = findNodeById(root, curNode.pid);
291             if (!hasSelChildrenNodes(tElement, curNode)) {
292                 var pOption = tElement.find("option[value='" + curNode.value + "']");
293                 if (pOption.length > 0) pOption.prop("selected", false);
294             }
295         }
296     }
297     //从树中寻找某个id的节点
298     function findNodeById(node, id) {
299         if (node.value == id) {
300             return node;
301         }
302         else {
303             if (node.children) {
304                 for (var i = 0; i < node.children.length; i++) {
305                     var rsNode = findNodeById(node.children[i], id);
306                     if (rsNode != null) return rsNode;
307                 }
308             }
309         }
310         return null;
311     }
312     //判断某个节点的子节点是否被选中
313     function hasSelChildrenNodes(tElement, node) {
314         if (node.children) {
315             for (var i = 0; i < node.children.length; i++) {
316                 var pOption = tElement.find("option[value='" + node.children[i].value + "']:selected");
317                 if (pOption.length > 0) return true;
318             }
319         }
320         return false;
321     }
322 })(jQuery, window, document);

 

转载于:https://www.cnblogs.com/liuhua4451/p/5056995.html

  • 点赞
  • 收藏
  • 分享
  • 文章举报
dijiao1893 发布了0 篇原创文章 · 获赞 0 · 访问量 136 私信 关注
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: