zoukankan      html  css  js  c++  java
  • 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);
  • 相关阅读:
    盒子垂直水平居中
    Sahi (2) —— https/SSL配置(102 Tutorial)
    Sahi (1) —— 快速入门(101 Tutorial)
    组织分析(1)——介绍
    Java Servlet (1) —— Filter过滤请求与响应
    CAS (8) —— Mac下配置CAS到JBoss EAP 6.4(6.x)的Standalone模式(服务端)
    JBoss Wildfly (1) —— 7.2.0.Final编译
    CAS (7) —— Mac下配置CAS 4.x的JPATicketRegistry(服务端)
    CAS (6) —— Nginx代理模式下浏览器访问CAS服务器网络顺序图详解
    CAS (5) —— Nginx代理模式下浏览器访问CAS服务器配置详解
  • 原文地址:https://www.cnblogs.com/liuhua4451/p/5056995.html
Copyright © 2011-2022 走看看