BootStrap-DualListBox怎樣改造成為雙樹


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[n].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[n],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[n],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[n], 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[n].value + "']").prop("selected", true);
247                 selAllChildNodes(tElement, node.children[n]);
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[n].value + "']").prop("selected", false);
256                 unSelAllChildNodes(tElement, node.children[n]);
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[n], 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);

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM