• 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);
  • 相关阅读:
    当年偶然发现的 Java Bug(JDK 9及之前仍未修复)
    Centos 网卡命名规范及信息查看(物理网卡,虚拟网卡)
    Git 合并多个 commit,保持历史简洁
    Java 常用验证方法(commons-validator,hutool)
    Linux 日常操作(质量团队培训材料)
    Linux 帮助命令及工具(tldr,man,help,info)
    springmvc返回html页面解决方案
    二进制和十进制来回转换
    二进制按位与(&) 按位或(|) 异或运算(^)
    Spring容器和springmvc容器的区别联系
  • 原文地址:https://www.cnblogs.com/liuhua4451/p/5056995.html
Copyright © 2020-2023  润新知